Aspnetcore.docs: Multiple authentication schemes

Created on 3 Jan 2020  Â·  16Comments  Â·  Source: dotnet/AspNetCore.Docs

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.


Document Details

⚠ Do not edit this section. It is required for docs.microsoft.com ➟ GitHub issue linking.

Security-PU Source - Docs.ms

Most helpful comment

@udlose please use the subscribe button to get updates.

I have been subscribed. Haven't seen any updates and was checking in.

All 16 comments

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.

Was this page helpful?
0 / 5 - 0 ratings