33using System . Linq ;
44using System . Security . Claims ;
55using System . Threading . Tasks ;
6+ using Auth0 . AspNetCore . Authentication . CustomDomains ;
67using Microsoft . AspNetCore . Authentication . Cookies ;
78using Microsoft . AspNetCore . Authentication . OpenIdConnect ;
89using Microsoft . AspNetCore . Http ;
@@ -18,7 +19,7 @@ public class BackchannelLogoutHandler
1819 private readonly ILogoutTokenHandler _tokenHandler ;
1920 private readonly string _authenticationScheme ;
2021
21- public BackchannelLogoutHandler ( ILogoutTokenHandler tokenHandler )
22+ public BackchannelLogoutHandler ( ILogoutTokenHandler tokenHandler )
2223 : this ( tokenHandler , Auth0Constants . AuthenticationScheme )
2324 {
2425 }
@@ -48,7 +49,17 @@ public async Task HandleRequestAsync(HttpContext context)
4849 . GetRequiredService < IOptionsSnapshot < OpenIdConnectOptions > > ( )
4950 . Get ( _authenticationScheme ) ;
5051
51- var principal = await ValidateLogoutToken ( logoutToken , oidcOptions , context ) ;
52+ var customDomainsOptions = context . RequestServices
53+ . GetService < IOptionsMonitor < Auth0CustomDomainsOptions > > ( )
54+ ? . Get ( _authenticationScheme ) ;
55+ var isMcdEnabled = customDomainsOptions ? . IsMultipleCustomDomainsEnabled == true ;
56+
57+ if ( isMcdEnabled )
58+ {
59+ ValidateIssuerMatchesResolvedDomain ( logoutToken , context ) ;
60+ }
61+
62+ var principal = await ValidateLogoutToken ( logoutToken , oidcOptions , context , isMcdEnabled ) ;
5263
5364 if ( principal != null )
5465 {
@@ -84,7 +95,46 @@ await context.WriteErrorAsync(400, "invalid_request",
8495 }
8596 }
8697
87- private async Task < ClaimsPrincipal > ValidateLogoutToken ( String token , OpenIdConnectOptions oidcOptions , HttpContext context )
98+ /// <summary>
99+ /// When MCD is enabled, extracts the issuer from the unverified token and validates it matches
100+ /// the domain resolved for the current request. This check happens BEFORE full JWT
101+ /// validation so we avoid fetching JWKS for tokens from the wrong tenant.
102+ /// </summary>
103+ private static void ValidateIssuerMatchesResolvedDomain ( string token , HttpContext context )
104+ {
105+ var unverifiedIssuer = ExtractUnverifiedIssuer ( token ) ;
106+
107+ var resolvedDomain = context . GetResolvedDomain ( ) ;
108+
109+ if ( string . IsNullOrWhiteSpace ( resolvedDomain ) )
110+ {
111+ throw new LogoutTokenValidationException (
112+ "Unable to resolve domain for this request. Ensure DomainResolver is configured." ) ;
113+ }
114+
115+ var normalizedIssuer = Utils . ToAuthority ( unverifiedIssuer ) ;
116+ var normalizedResolved = Utils . ToAuthority ( resolvedDomain ) ;
117+
118+ if ( ! string . Equals ( normalizedIssuer , normalizedResolved ) )
119+ {
120+ throw new LogoutTokenValidationException ( "Logout token issuer does not match the resolved domain." ) ;
121+ }
122+ }
123+
124+ /// <summary>
125+ /// Reads the JWT without signature validation to extract the issuer claim.
126+ /// Throws <see cref="LogoutTokenValidationException"/> if the token is malformed.
127+ /// Note: This does not validate the signature or any other JWT claims.
128+ /// </summary>
129+ private static string ExtractUnverifiedIssuer ( string token )
130+ {
131+ var handler = new JwtSecurityTokenHandler ( ) ;
132+ if ( ! handler . CanReadToken ( token ) )
133+ throw new LogoutTokenValidationException ( "Logout token is malformed or not a valid JWT." ) ;
134+ return handler . ReadJwtToken ( token ) . Issuer ;
135+ }
136+
137+ private async Task < ClaimsPrincipal > ValidateLogoutToken ( string token , OpenIdConnectOptions oidcOptions , HttpContext context , bool isMcdEnabled )
88138 {
89139 OpenIdConnectConfiguration ? configuration = null ;
90140
@@ -93,13 +143,19 @@ private async Task<ClaimsPrincipal> ValidateLogoutToken(String token, OpenIdConn
93143 configuration = await oidcOptions . ConfigurationManager . GetConfigurationAsync ( context . RequestAborted ) ;
94144 }
95145
146+ var validIssuer = isMcdEnabled
147+ ? Utils . ToAuthority ( context . GetResolvedDomain ( )
148+ ?? throw new LogoutTokenValidationException (
149+ "Unable to resolve domain for this request. Ensure DomainResolver is configured." ) )
150+ : oidcOptions . TokenValidationParameters . ValidIssuer ;
151+
96152 var tokenValidationParameters = new TokenValidationParameters
97153 {
98154 ValidateAudience = true ,
99155 ValidateIssuer = true ,
100156 ValidateLifetime = true ,
101157 RequireExpirationTime = true ,
102- ValidIssuer = oidcOptions . TokenValidationParameters . ValidIssuer ,
158+ ValidIssuer = validIssuer ,
103159 ValidAudience = oidcOptions . TokenValidationParameters . ValidAudience ,
104160 } ;
105161
0 commit comments