I am trying to create a sample application using multiple authentication schemes (AAD B2C and another AAD tenant). If I configure only one of them (does not matter which one) it works like a charm and everything works correctly. If I configure both of them (as in the provided example), however mention only one of them in the default policy, it also works (only for that scheme):
services.AddAuthentication()
.AddJwtBearer("B2C", jwtOptions =>
{
jwtOptions.Authority = "my-b2c-authority";
jwtOptions.Audience = "my-audience";
})
.AddJwtBearer("AAD", jwtOptions =>
{
jwtOptions.Authority = "my-aad-authority";
jwtOptions.Audience = "my-audience";
});
services.AddAuthorization(options =>
{
options.DefaultPolicy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.AddAuthenticationSchemes("B2C")
.Build();
});
However, if I configure both and mention them in the default policy:
services.AddAuthorization(options =>
{
options.DefaultPolicy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.AddAuthenticationSchemes("B2C", "AAD")
.Build();
});
it seems that the token gets validated twice (against each configured scheme) and therefore it fails - I am suspecting that as I can see the following messages in the debug window:
Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerHandler:Information: Successfully validated the token.
...
Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerHandler:Information: Failed to validate the token.
Microsoft.IdentityModel.Tokens.SecurityTokenSignatureKeyNotFoundException: IDX10501: Signature validation failed. Unable to match keys:
kid: '[PII is hidden]',
token: '[PII is hidden]'.
at System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.ValidateSignature(String token, TokenValidationParameters validationParameters)
at System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.ValidateToken(String token, TokenValidationParameters validationParameters, SecurityToken& validatedToken)
at Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerHandler.HandleAuthenticateAsync()
...
I guess I am missing something and I have not been able to figure it out on my own. Would be grateful for any hints regarding what might be wrong.
⚠Do not edit this section. It is required for docs.microsoft.com ➟ GitHub issue linking.
Moved to Master issue for: Authorize with a specific scheme #16190
@Rick-Anderson
I also have this same issue. Should I be watching this Issue to track a resolution or https://github.com/aspnet/AspNetCore.Docs/issues/16190?
@snake-net yes, watch them both
Multiple authentication schemes
@HaoK this is similar to #15791 and tracked in #16190
Any updates on this? I too cannot add 2 schemes
This issue is not resolved, why are the tickets closed?
Moved to Master issue for: Authorize with a specific scheme #16190
@RickAndMSFT is there a resolution to this issue?
@snake-net , have you found an solution or workaround to this issue?
Nothing besides trying to use a single authority. I think it also worked in the previous version of the .NET Core, however we could not downgrade and therefore we have not checked that possibility.
I was able to workaround this issue by copying the JwtBearerHandler class @ (src/Security/Authentication/JwtBearer/src/JwtBearerHandler.cs) into my project and replaced the hard coded scheme check with a check against the current Scheme object:
Replaced this:
https://github.com/dotnet/aspnetcore/blob/1f76cce14ac4e4698a554b65a24f28000b50396e/src/Security/Authentication/JwtBearer/src/JwtBearerHandler.cs#L74-L77
if (authorization.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase))
{
token = authorization.Substring("Bearer ".Length).Trim();
}
With this:
if (authorization.StartsWith($"{Scheme.Name} ", StringComparison.OrdinalIgnoreCase))
{
token = authorization.Substring($"{Scheme.Name} ".Length).Trim();
}
I then created some custom AddJwtAuthentication extensions referencing my modified JwtBearerHandler class (CustomJwtBearerHandler) and .
i.e.
public static AuthenticationBuilder AddDefaultJwtAuthentication(this AuthenticationBuilder builder,
string schema, string displayName, Action<JwtBearerOptions> configureOptions)
{
return builder.AddScheme<JwtBearerOptions, CustomJwtBearerHandler>(schema, displayName, configureOptions);
}
public static AuthenticationBuilder AddDefaultJwtAuthentication(this AuthenticationBuilder builder,
string schema, Action<JwtBearerOptions> configureOptions)
{
return builder.AddScheme<JwtBearerOptions, CustomJwtBearerHandler>(schema, schema, configureOptions);
}
public static AuthenticationBuilder AddDefaultJwtAuthentication(this AuthenticationBuilder builder,
Action<JwtBearerOptions> configureOptions)
{
return builder.AddScheme<JwtBearerOptions, CustomJwtBearerHandler>(
JwtBearerDefaults.AuthenticationScheme,
JwtBearerDefaults.AuthenticationScheme,
configureOptions);
}
And used those extensions to configure my Jwt authentication.
Seems to work fine, but If there are any security concerns please let me know.
@Rick-Anderson any update on this issue?
@udlose please use the subscribe button to get updates.
@udlose please use the subscribe button to get updates.
I have been subscribed. Haven't seen any updates and was checking in.
@snake-net, have you try to use PolicyScheme. It worked in my case.
Schemes configuration would look something like this:
services.AddAuthentication()
.AddJwtBearer("B2C", jwtOptions =>
{
jwtOptions.Authority = "my-b2c-authority";
jwtOptions.Audience = "my-audience";
})
.AddJwtBearer("AAD", jwtOptions =>
{
jwtOptions.Authority = "my-aad-authority";
jwtOptions.Audience = "my-audience";
})
.AddPolicyScheme("B2C_OR_AAD", "B2C_OR_AAD", options =>
{
options.ForwardDefaultSelector = context =>
{
string authorization = context.Request.Headers[HeaderNames.Authorization];
if (!string.IsNullOrEmpty(authorization))
{
if (authorization.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase))
{
var token = authorization.Substring("Bearer ".Length).Trim();
var jwtHandler = new JwtSecurityTokenHandler();
if (jwtHandler.CanReadToken(token))
{
var jwtToken = jwtHandler.ReadJwtToken(token);
if (jwtToken.Issuer.Equals("my-b2c-authority"))
{
return "B2C";
}
}
}
}
return "AAD";
};
});
And then use the PolicyScheme on the AuthorizationPolicy:
services.AddAuthorization(options =>
{
options.DefaultPolicy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.AddAuthenticationSchemes("B2C_OR_AAD")
.Build();
});
This way the policy will run the B2C_OR_AAD scheme, and the B2C_OR_AAD scheme will forward select to the actual scheme based on the ForwardDefaultSelector delegate.
@jamir-araujo I had to postpone working on it for a while. Thanks for sharing your solution, will definitely give it a try once AAD B2C comes back into the landscape.
Most helpful comment
I have been subscribed. Haven't seen any updates and was checking in.