Multiple Authentication Schemes are mutually exclusive
See original GitHub issueIs there an existing issue for this?
- I have searched the existing issues
Describe the bug
I have a React SPA and a Mobile App that calls a Web API protected by Azure AD OIDC.
- The React SPA uses the default JWT authentication scheme provided by the
AddMicrosoftIdentityWebApi()
extension - The Mobile App uses a custom HTTP Header-based authentication scheme
Goal: If AT LEAST one of the schemes succeeds then the user should be authenticated. Unfortunately the two schemes are mutually exclusive:
- If I set
AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme)
then the React SPA authenticates the user but the Mobile App returns 401 - If I set
AddAuthenticationSchemes(MobileAuthenticationDefaults.AuthenticationScheme)
then the Mobile App authenticates the user but the React SPA returns 401
Expected Behavior
Both React SPA and Mobile App should be able to authenticate using their separate authentication schemes.
Steps To Reproduce
startup.cs example with JWT default:
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddScheme<MobileAuthenticationSchemeOptions, MobileAuthenticationHandler>(MobileAuthenticationDefaults.AuthenticationScheme, null)
.AddMicrosoftIdentityWebApi(Configuration.GetSection("AzureAD:LycheeWebAPI"));
services.AddAuthorization(options =>
{
options.DefaultPolicy = new AuthorizationPolicyBuilder(
JwtBearerDefaults.AuthenticationScheme,
MobileAuthenticationDefaults.AuthenticationScheme)
.RequireAuthenticatedUser()
.Build();
});
services.AddScoped<IAuthenticationHandler, MobileAuthenticationHandler>();
...
var policy = new AuthorizationPolicyBuilder()
.AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme, MobileAuthenticationDefaults.AuthenticationScheme)
.RequireAuthenticatedUser()
.Build();
mvcOptions.Filters.Add(new AuthorizeFilter(policy));
...
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
MobileAuthenticationHandler:
public class MobileAuthenticationHandler : AuthenticationHandler<MobileAuthenticationSchemeOptions>
{
protected override Task<AuthenticateResult> HandleAuthenticateAsync()
{
// validation comes in here
if (!Request.Headers.ContainsKey(ApiConstants.MobileApiHttpHeader))
{
return Task.FromResult(AuthenticateResult.NoResult());
}
...
var claimsIdentity = new ClaimsIdentity(claims, nameof(MobileAuthenticationHandler));
var ticket = new AuthenticationTicket(
new ClaimsPrincipal(claimsIdentity), this.Scheme.Name);
return Task.FromResult(AuthenticateResult.Success(ticket));
}
MobileAuthenticationOptions.cs:
public class MobileAuthenticationSchemeOptions : AuthenticationSchemeOptions
{
}
MobileAuthenticationDefaults.cs:
public static class MobileAuthenticationDefaults
{
public const string AuthenticationScheme = "MobileAuthenticationScheme";
}
Exceptions (if any)
N/A
.NET Version
6.0.101
Anything else?
- ASP.NET Core 5
- VS 2022 Community
.NET SDK (reflecting any global.json):
Version: 6.0.101
Commit: ef49f6213a
Runtime Environment:
OS Name: Windows
OS Version: 10.0.19043
OS Platform: Windows
RID: win10-x64
Base Path: C:\Program Files\dotnet\sdk\6.0.101\
Host (useful for support):
Version: 6.0.3
Commit: c24d9a9c91
.NET SDKs installed:
6.0.101 [C:\Program Files\dotnet\sdk]
.NET runtimes installed:
Microsoft.AspNetCore.App 5.0.13 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 5.0.15 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 6.0.1 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 6.0.3 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.NETCore.App 5.0.13 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 5.0.15 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 6.0.1 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 6.0.3 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.WindowsDesktop.App 5.0.13 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Microsoft.WindowsDesktop.App 5.0.15 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Microsoft.WindowsDesktop.App 6.0.1 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Issue Analytics
- State:
- Created a year ago
- Reactions:1
- Comments:19 (13 by maintainers)
Top Results From Across the Web
Skip Authentication Schemes in ASP.NET Core
Learn how to fine-tune the authentication middleware and skip authentication schemes in ASP.NET Core; complete with examples!
Read more >Mutual authentication
Mutual authentication or two-way authentication refers to two parties authenticating each other at the same time in an authentication protocol.
Read more >How to Use Multiple Authentication Schemes in .NET
In this article, we are going to learn how to use multiple authentication schemes in .NET 6 at the same time.
Read more >5 Configuring User Authentication
For chained authentication, an authentication scheme contains multiple steps linked ... Redirection and use of header variables are mutually exclusive.
Read more >Asp.net multiple authenticaiton schemes | 32 comments
NET: Multiple Authentication Schemes Usually, in the Api you have one identity ... constants are generally used for lists of mutually exclusive elements....
Read more >Top Related Medium Post
No results found
Top Related StackOverflow Question
No results found
Troubleshoot Live Code
Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start FreeTop Related Reddit Thread
No results found
Top Related Hackernoon Post
No results found
Top Related Tweet
No results found
Top Related Dev.to Post
No results found
Top Related Hashnode Post
No results found
Top GitHub Comments
Or maybe we should just add a ne example of this pick first scheme that succeeds Authenticate to the policyschemes docs
I guess if we wanted to make this in the box, we could also just add the concept of
FallbackAuthenticateScheme
to our default implementation so this kind of thing just works for authenticate (check yourself for authentication, if you don’t have anything, then fallback to the FallbackAuthenticateScheme), its basically a second Foward, but instead of always forwarding, you only fallback