Handling Multiple Authentication Schemes in ASP.NET Core: Unlocking the Power of Flexible Security with .NET 8

What’s the Story About?
If you think managing authentication in ASP.NET Core means settling for one approach, imagine a world where you seamlessly handle multiple authentication schemes, all in the same application. Suddenly, you can support different authentication methods like JWT tokens, custom tokens, or even IdentityServer configurations, without breaking a sweat. Do you know how to implement these different schemes effectively in .NET 8? If you’re interested in building a robust and flexible authentication system, you should read this guide.
Why Multiple Authentication Schemes?
In modern applications, it’s common to support various clients and services, each requiring different authentication mechanisms. For example:
- Microservices Communication: Internal services might use JWT for API-to-API communication.
- User Authentication: External users may authenticate through third-party providers or custom token mechanisms.
- Legacy Systems: Integration with older systems might require distinct authentication protocols.
Instead of forcing all clients to adhere to a single authentication approach, we can leverage ASP.NET Core’s flexibility to set up multiple authentication schemes, providing the best of both worlds. Let’s dive into how we can accomplish this using .NET 8.
Setting Up Multiple Authentication Schemes in .NET 8
In this tutorial, we’ll implement multiple authentication schemes using ASP.NET Core with .NET 8, including JWT authentication for different identity servers and a custom authentication handler. This will allow us to handle tokens from different sources and support custom logic for token validation.
1. Configuring the Authentication Schemes
To start, we’ll set up the authentication services in Program.cs or a dedicated service extension method. Our goal is to support multiple JWT token sources, such as IdentityServerA and IdentityServerB, and a custom authentication scheme for specialized token handling.
public static class DIExtension
{
public static void SetupAuthentication(this IServiceCollection services, IConfiguration config)
{
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidAudience = config["IdentitySeverA:Audience"],
ValidIssuer = config["IdentitySeverA:Issuer"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("SecretKeyForIssuerA"))
};
})
.AddJwtBearer("Scheme_ServerB", options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidAudience = config["IdentitySeverB:Audience"],
ValidIssuer = config["IdentitySeverB:Issuer"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("SecretKeyForIssuerB"))
};
})
.AddScheme<CustomAuthSchemeOptions, CustomAuthenticationHandler>("CustomToken", options => { });
}
}2. Implementing a Custom Authentication Handler
The custom authentication handler will enable us to perform more complex authentication scenarios, such as validating tokens against a database. Below is an example of how you can create a custom authentication handler.
public class CustomAuthenticationHandler : AuthenticationHandler<CustomAuthSchemeOptions>
{
private readonly ITokenRepository _tokenRepository;
public CustomAuthenticationHandler(
IOptionsMonitor<CustomAuthSchemeOptions> options,
ILoggerFactory logger,
UrlEncoder encoder,
ISystemClock clock,
ITokenRepository tokenRepository)
: base(options, logger, encoder, clock)
{
_tokenRepository = tokenRepository;
}
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
if (!Request.Headers.TryGetValue("Authorization", out var customToken))
return AuthenticateResult.Fail("No token provided");
var token = customToken.ToString().GetAccessToken();
var dbToken = await _tokenRepository.GetAsync(token);
if (dbToken == null)
return AuthenticateResult.Fail("Invalid token");
var claims = new ClaimsIdentity(nameof(CustomAuthenticationHandler));
claims.AddClaim(new Claim("userId", dbToken.UserId));
var ticket = new AuthenticationTicket(new ClaimsPrincipal(claims), Scheme.Name);
return AuthenticateResult.Success(ticket);
}
}3. Using a Policy Scheme to Route Requests to the Appropriate Authentication Handler
The AddPolicyScheme method allows you to dynamically choose which authentication scheme to use based on the incoming request. Here’s how we can configure it.
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddPolicyScheme(JwtBearerDefaults.AuthenticationScheme, JwtBearerDefaults.AuthenticationScheme, options =>
{
options.ForwardDefaultSelector = context =>
{
var token = context.Request.Headers[HeaderNames.Authorization].ToString().GetAccessToken();
var jwtHandler = new JwtSecurityTokenHandler();
if (jwtHandler.CanReadToken(token))
{
var issuer = jwtHandler.ReadJwtToken(token).Issuer;
return issuer == config["IdentitySeverA:Issuer"] ? "Scheme_ServerA" : "Scheme_ServerB";
}
return "CustomToken";
};
});Controller Example: Using Multiple Authentication Schemes
In your controllers, you can specify which authentication scheme should be used for each endpoint.
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
[HttpGet("serverA")]
[Authorize]
public IActionResult GetServerAData()
{
// Fetch data secured by IdentityServerA token
return Ok("Data from Server A");
}
[HttpGet("serverB")]
[Authorize(AuthenticationSchemes = "Scheme_ServerB")]
public IActionResult GetServerBData()
{
// Fetch data secured by IdentityServerB token
return Ok("Data from Server B");
}
[HttpGet("custom")]
[Authorize(AuthenticationSchemes = "CustomToken")]
public IActionResult GetCustomTokenData()
{
// Fetch data secured by custom token
return Ok("Data from Custom Token");
}
}Testing the Authentication Flow
To verify the setup:
- Use Swagger: The application can be configured with Swagger to support token-based authentication testing.
- Postman or HTTP Client: Send requests with different tokens to see how the policy scheme routes to the correct authentication handler.
Conclusion
By setting up multiple authentication schemes in ASP.NET Core with .NET 8, you can effortlessly manage diverse authentication requirements for various client scenarios. This approach not only improves the flexibility of your authentication infrastructure but also makes your application future-proof for integrating additional authentication methods as needed.
Final Thoughts: Is Your Authentication Secure?
If you’ve ever struggled with rigid authentication requirements, .NET 8’s flexible configuration options offer a game-changing solution. With a little bit of setup, you can unlock a powerful multi-authentication architecture, empowering your applications to securely interact with multiple identity providers. Want to learn more about authentication best practices? Stay tuned for our next guide on securing microservices in .NET 8.
Don’t miss out! Follow this guide to elevate your ASP.NET Core projects, and share this article with your team to stay ahead in building secure applications.




