Auth with multiple Identity Providers in ASP.NET Core
By FoxLearn 3/3/2025 8:51:28 AM 235
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 theAuthorization
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.
- The name 'Session' does not exist in the current context
- Implementing Two-Factor Authentication with Google Authenticator in ASP.NET Core
- How to securely reverse-proxy ASP.NET Core
- How to Retrieve Client IP in ASP.NET Core Behind a Reverse Proxy
- Only one parameter per action may be bound from body in ASP.NET Core
- The request matched multiple endpoints in ASP.NET Core
- How to Create a custom model validation attribute in ASP.NET Core
- How to disable ModelStateInvalidFilter in ASP.NET Core