1

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?

3
  • When you receive a bearer token, how do you know which authentication provider it was for? They're usually completely opaque. Commented Feb 26, 2024 at 22:38
  • I don't see anywhere in that document that explains that. I've done multi-scheme authentication using a custom scheme for dispatch, but there has to be a way to differentiate them for that to work. You didn't do exactly what it showed so maybe there's some magic inside of EnableTokenAcquisitionToCallDownstreamApi. Commented Feb 26, 2024 at 23:00
  • Yeah after playing around with it, I was able to find out that you can operate under this assumption my manually naming each type specifically. Then you can specify which one you want to use in the Attribute by selecting the relevant scheme. This does not solve my problem though, since the authorize step of my requester is just handling the default regardless. Assuming the solution is to override the actual interceptor so once I figure that out I'll post it as a solution. Commented Feb 27, 2024 at 2:13

1 Answer 1

1

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);
    }
}
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.