Auth with multiple Identity Providers in ASP.NET Core

By FoxLearn 3/3/2025 8:51:28 AM   235
To authenticate with multiple identity providers in ASP.NET Core, you can configure different authentication schemes for each identity provider (such as Auth0, Azure AD, or OpenIddict) and handle token validation accordingly.

The ASP.NET Core API in question needs to accept access tokens from three different identity providers: Auth0, OpenIddict, and Azure AD. These tokens are acquired using OAuth2.

In this implementation, self-contained access tokens are used (which are signed but not encrypted), though this can be modified to support encrypted tokens. Each token must be validated thoroughly, including validating the signature. We use a custom ASP.NET Core authentication handler to validate claims from different identity providers.

Steps to Auth with Multiple Identity Providers in ASP.NET Core

You will need several NuGet packages for handling authentication with different identity providers. Below are some key ones:

  • Microsoft.AspNetCore.Authentication.JwtBearer for handling JWT tokens (for OpenIddict, Auth0, Azure AD, etc.).
  • Microsoft.Identity.Web for Azure AD, if you want to use the Azure AD library (though this might interfere with other schemes).
  • Microsoft.AspNetCore.Authentication.OpenIdConnect for other identity providers.

Install them using the NuGet package manager or via the terminal:

dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer
dotnet add package Microsoft.Identity.Web

In Startup.cs or Program.cs (depending on ASP.NET Core version), you will need to configure multiple authentication schemes.

For example, Configure JWT Bearer authentication for Auth0, Azure AD, and OpenIddict.

services.AddAuthentication(options =>
{
    options.DefaultScheme = "UNKNOWN";
    options.DefaultChallengeScheme = "UNKNOWN";
})
.AddJwtBearer("Auth0", options =>
{
    options.Authority = Configuration["Auth0:Authority"];
    options.Audience = Configuration["Auth0:Audience"];
    options.TokenValidationParameters = new TokenValidationParameters
    {
        ValidateIssuer = true,
        ValidateAudience = true,
        ValidateIssuerSigningKey = true,
        ValidAudiences = Configuration.GetSection("ValidAudiences").Get<string[]>(),
        ValidIssuers = Configuration.GetSection("ValidIssuers").Get<string[]>()
    };
});

Similarly, you can configure Azure AD token validation (using AddJwtBearer) and OpenIddict tokens. One key point to note is that certain client libraries (like Microsoft.Identity.Web) may interfere with the default ASP.NET Core middleware, so it's preferable to use the default implementation for supporting multiple schemes.

For Azure AD:

.AddJwtBearer("AzureAD", options =>
{
    options.MetadataAddress = Configuration["AzureAd:MetadataAddress"];
    options.Authority = Configuration["AzureAd:Authority"];
    options.Audience = Configuration["AzureAd:Audience"];
    options.TokenValidationParameters = new TokenValidationParameters
    {
        ValidateIssuer = true,
        ValidateAudience = true,
        ValidateIssuerSigningKey = true,
        ValidAudiences = Configuration.GetSection("ValidAudiences").Get<string[]>(),
        ValidIssuers = Configuration.GetSection("ValidIssuers").Get<string[]>()
    };
});

For OpenIddict, avoid using the OpenIddict client library to prevent conflicts with other schemes. Instead, stick with ASP.NET Core’s default approach for handling JWT tokens.

.AddJwtBearer("OpenIddict", options =>
{
    options.Authority = Configuration["OpenIddict:Authority"];
    options.Audience = Configuration["OpenIddict:Audience"];
    options.TokenValidationParameters = new TokenValidationParameters
    {
        ValidateIssuer = true,
        ValidateAudience = true,
        ValidateIssuerSigningKey = true,
        ValidAudiences = Configuration.GetSection("ValidAudiences").Get<string[]>(),
        ValidIssuers = Configuration.GetSection("ValidIssuers").Get<string[]>()
    };
});

In the above example:

  • AddJwtBearer("Auth0") configures authentication for Auth0.
  • AddJwtBearer("AzureAD") configures authentication for Azure AD.
  • AddJwtBearer("OpenIddict") configures authentication for OpenIddict.

Handling Token Forwarding with ForwardDefaultSelector

To implement the logic of forwarding the token to the correct validation scheme, you use the AddPolicyScheme method. This ensures that the default authentication scheme is set to UNKNOWN, and the token is forwarded to the appropriate scheme based on the issuer.

// Add Policy for choosing the right authentication scheme
services.AddAuthentication().AddPolicyScheme("UNKNOWN", "UNKNOWN", options =>
{
    options.ForwardDefaultSelector = context =>
    {
        var authorization = context.Request.Headers[HeaderNames.Authorization];
        if (!string.IsNullOrEmpty(authorization) && authorization.StartsWith("Bearer "))
        {
            var token = authorization.Substring("Bearer ".Length).Trim();
            var jwtHandler = new JwtSecurityTokenHandler();

            if (jwtHandler.CanReadToken(token))
            {
                var issuer = jwtHandler.ReadJwtToken(token).Issuer;

                // Forward to appropriate scheme based on token issuer
                if (issuer == Configuration["OpenIddict:Issuer"])
                    return "OpenIddict";

                if (issuer == Configuration["Auth0:Issuer"])
                    return "Auth0";

                if (issuer == Configuration["AzureAD:Issuer"])
                    return "AzureAD";
            }
        }

        return "AzureAD"; // Default to Azure AD if unknown
    };
});

In this example:

  • The ForwardDefaultSelector checks the Authorization header of the request.
  • It then reads the JWT token and uses the Issuer to select which identity provider (Auth0, Azure AD, OpenIddict) the token should be validated against.

Authorization Policy

Once the tokens are validated, you can add custom authorization policies that check specific claims. The AddAuthorization method is used to add a policy that applies to all identity providers, which is then enforced by a custom AuthorizationHandler.

services.AddSingleton<IAuthorizationHandler, AllSchemesHandler>();

services.AddAuthorization(options =>
{
    options.AddPolicy("AllPolicy", policy =>
    {
        policy.Requirements.Add(new AllSchemesRequirement());
    });
});

The AllSchemesHandler class checks claims specific to each identity provider, such as the issuer or the azp claim. It ensures that the token's claims meet the requirements set by the policy.

using Microsoft.AspNetCore.Authorization;

namespace WebApi;

public class AllSchemesHandler : AuthorizationHandler<AllSchemesRequirement>
{
    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context,
        AllSchemesRequirement requirement)
    {
        var issuer = context.User.Claims.FirstOrDefault(c => c.Type == "iss")?.Value;

        if (issuer == Configuration["OpenIddict:Issuer"]) // OpenIddict
        {
            var scopeClaim = context.User.Claims.FirstOrDefault(c => c.Type == "scope" && c.Value == "your value");
            if (scopeClaim != null)
            {
                context.Succeed(requirement);
            }
        }

        if (issuer == Configuration["Auth0:Issuer"]) // Auth0
        {
            var azpClaim = context.User.Claims.FirstOrDefault(c => c.Type == "azp" && c.Value == "your value");
            if (azpClaim != null)
            {
                context.Succeed(requirement);
            }
        }

        if (issuer == Configuration["AzureAD:Issuer"]) // AAD
        {
            var azpClaim = context.User.Claims.FirstOrDefault(c => c.Type == "azp" && c.Value == "your value");
            if (azpClaim != null)
            {
                context.Succeed(requirement);
            }
        }

        return Task.CompletedTask;
    }
}

Controller with Multiple Identity Providers

Finally, you can enforce authorization policies in your controllers. The AuthenticationSchemes property allows you to specify multiple authentication schemes, and the Policy ensures that tokens are validated according to the specific identity provider's rules.

[Authorize(AuthenticationSchemes = "Auth0, AzureAD, OpenIddict", Policy = "AllPolicy")]
[Route("api/[controller]")]
public class ValuesController : Controller
{
    [HttpGet]
    public IEnumerable<string> Get()
    {
        return new string[] { "data 1 from the api", "data 2 from the api" };
    }
}

By following the above steps, you can authenticate an ASP.NET Core API with multiple identity providers.