Multiple Bearer Authentication Schemes in ASP.NET Core Using Microsoft.Identity.Web

198 Views Asked by At

I understand it is possible using the custom JWT Providers to add multiple Authentication Schemes in one application. In the following documentation: https://github.com/AzureAD/microsoft-identity-web/wiki/Multiple-Authentication-Schemes, it is stated that "Microsoft Identity Web now supports multiple authentication schemes, as of v.1.11.0."

My problem is as follows: I would like to use the Bearer token as a method of authentication from an Azure AD Resource in one tenant, and a Azure AD B2C resource in another tenant.

I have tried the following:

builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
            .AddMicrosoftIdentityWebApi(options =>
            {
                configuration.Bind("AzureAdB2C", options);
            }, options => 
            {
                configuration.Bind("AzureAdB2C", options);
            }, Constants.Bearer, true);

builder.Services.AddAuthentication()
            .AddMicrosoftIdentityWebApi(options =>
            {
                configuration.Bind("AzureAd", options);

                options.TokenValidationParameters.ValidAudiences = ["some-valid-audience-value-i-am-hiding-from-stack-overflow"];

            }, options => { configuration.Bind("AzureAd", options); }, Constants.Bearer, true );

This throws the following exception:

System.InvalidOperationException: 'Scheme already exists: Bearer'

Following this, I can change the name of the Constants.Bearer to a secondary value to avoid this exception: IE:" "Bearer2". Now, only the first AzureAdB2C tokens work, and the secondary azure ad tokens are failing at this point.

Is there a way to make AddMicrosoftIdentityWebApi attempt to decode two separate bearer tokens from two separate resources?

1

There are 1 best solutions below

0
Mason A On

I ended up getting this working in the following way:

        builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
        .AddMicrosoftIdentityWebApi(options =>
        {
            configuration.Bind("AzureAd", options);

            options.TokenValidationParameters.ValidAudiences = [configuration["AzureAd:ClientId"]];
        }, options => { configuration.Bind("AzureAd", options); })
        .EnableTokenAcquisitionToCallDownstreamApi(_ => { })
        .AddInMemoryTokenCaches();

        builder.Services.AddAuthentication()
        .AddMicrosoftIdentityWebApi(configuration, "AzureAdB2C", "B2CScheme")
        .EnableTokenAcquisitionToCallDownstreamApi();

Then in ASP .NET:

[Authorize(AuthenticationSchemes = "B2CScheme,Bearer")]

This should allow for both of the schemas to function.

Using GraphQL was a little more complicated but this was my use case so I will include it here. Using GraphQL with HotChocolate:

        serviceCollection.AddGraphQLServer()
        .AddHttpRequestInterceptor<AuthenticationInterceptor>()
        .AddAuthorization();

You then can write that AuthenticationInterceptor here:

public class AuthenticationInterceptor : DefaultHttpRequestInterceptor
{
    public override async ValueTask OnCreateAsync(HttpContext context, IRequestExecutor requestExecutor, IQueryRequestBuilder requestBuilder, CancellationToken cancellationToken)
    {

        var result = await context.AuthenticateAsync("B2CScheme");
        if (!result.Succeeded)
        {
            result = await context.AuthenticateAsync();
        }
        //not an else here because we reassign the value of result. Just looks confusing
        if (result.Succeeded)
        {
            context.User = result.Principal;
        }
        await base.OnCreateAsync(context, requestExecutor, requestBuilder, cancellationToken);
    }
}