Net 8 Blazor Web App (Interactive server w/ prerendering ) - multiple schemes doesn't work
- Dominant language
- C#
- Stars
- 38.4k
- Forks
- 10.9k
- Avg merge
- 2d 6h
- Merged PRs (30d)
- 290
Description
### Is there an existing issue for this?
- [X] I have searched the existing issues
### Describe the bug
The application I work with utilizes two seperate authentication schemes:
- Microsoft entra id for internal users + Cookie
- An external OIDC identity provider for external users + Cookie
Regarding routing, this is the general structure:
- **In addition**, any blazor page whose path starts with "/internal" is authorized on page with custom authorization policies to check for specific claims, for example `...RequireClaim(ctx=> ctx.FindFirst(InternalUserIdClaimType) is not null)`
- **Same goes for internal users.**
- For authenticated users, they simply dont use the same paths/route in the application.
However, they share the same entry point, route ("/"), which is the landing page for the application. This is configured with `[AllowAnonymous]`.
I've followed the instructions here for configuring multiple policy schemes:
https://learn.microsoft.com/en-us/aspnet/core/security/authentication/policyschemes?view=aspnetcore-8.0
My configuration looks something like this:
##Configuration
### Authentication:
```cs
services.AddAuthentication(opts =>
{
opts.DefaultScheme = "multischeme";
})
.AddPolicyScheme("multischeme", "Internal or External", opts =>
{
opts.ForwardDefaultSelector = ctx =>
{
if (ctx.Request.Path.StartsWithSegments("/auth/internal") || ctx.Request.Path.StartsWithSegments("/internal/somepage") )
{
return "InternalScheme";
}
// Does not allowed undefined, so we have to return one of the schemes
return "ExternalScheme";
};
})
.AddOpenIdConnect("InternalScheme", "Internal auth scheme", options =>
{
options.SignInScheme = "InternalCookieScheme"; // <---- CUSTOM COOKIE SCHEME
options.Authority = "xxxxxxxxx";
options.ClientId = "xxxxxxxxxxxxx";
options.ClientSecret = "xxxxxxxxxx";
options.CallbackPath = new PathString("/xxxx/return");
options.ResponseType = "code";
options.RequireHttpsMetadata = true;
options.Events = new OpenIdConnectEvents
{
OnTokenValidated = ctx =>
{
// abbreviated, reading some claims, adding some custom claims to a new custom identity
// custom claim is used to authorize internal users
return Task.CompletedTask;
}
};
})
.AddCookie("InternalCookieScheme", "Internal auth cookie scheme", options =>
{
options.Cookie.Name = "InternalAuthCookie";
// abbreviated...
})
.AddOpenIdConnect("ExternalScheme", "External auth scheme", options =>
{
options.SignInScheme = "ExternalCookieScheme"; // <---- CUSTOM COOKIE SCHEME
options.Authority = "XXXXXXXXXXXXX";
options.ClientId = "xxxxxx";
options.ClientSecret = "xxxxx";
options.CallbackPath = new PathString("/xxxxxx/callback");
options.ResponseType = "code";
options.Scope.Clear();
options.Scope.Add("openid");
options.RequireHttpsMetadata = true;
options.Events = new OpenIdConnectEvents
{
OnTokenValidated = async ctx =>
{
// abbreviated, reading some claims, adding some custom claims to a new custom identity
// custom claim is used to authorize external users
return Task.CompletedTask;
}
};
})
.AddCookie("ExternalCookieScheme", "External auth cookie scheme", options =>
{
options.Cookie.Name = "ExternalAuthCookie";
// abbreviated...
});
```
### Authorization:
```cs
services.AddAuthorization(config =>
{
// INTERNAL user policies
config.AddPolicy(PolicyNames.IS_INTERNAL_USER,
policy =>
{
policy.AddRequirements(new IsInternalUserRequirement());
});
// EXTERNAL user policies
config.AddPolicy(PolicyNames.IS_EXTERNAL_USER,
policy =>
{
policy.AddRequirements(new IsExternalUserRequirement());
});
});
```
In summary, I have one combined scheme ("Multischeme") that wraps around "InternalScheme" and "Externalscheme", which both respectively connects to "InternalCookieScheme" and "ExternalCookieScheme".
### Endpoints
In addition, i have 2 controller endpoints for each specific scheme:
- /auth/external/login
- /auth/external/signout
- /auth/internal/login
- /auth/internal/signout
### Main configuration (program.cs):
- Auth handlers are registered earlier
- Controllers are added

### Routes.Razor:
- Has AuthorizeRouteView

### App.Razor:

As long as "ExternalScheme" is the default scheme, I can sign in / sign-out with the external OIDC provider and authorize just fine with it.
However, I cant authorize with internal user. I manage to sign in with it, Cookie gets set with values, but under `...context.User.Identittes`there is no user with those claims available in authorization.
Appreciate any help with this. I'm stomped! 😣
### Expected Behavior
The claims from both schemes should be available when signing in.
Authhandlers should be able to find the claims for both signed in schemes when executing authorization handlers & requirements.
Only the default identity provided from the default scheme seems to be available when dooing authorization (both in authorizationhandlers, but also in controllers!)
### Steps To Reproduce
Unfortunately i'm not at liberty to expose the external providers provided. I hope the code i've provided will be sufficient to reproduce the issue.
### Exceptions (if any)
None.
### .NET Version
8.0.303
### Anything else?
Another person on stackover flow seems to have a similar issue (unresolved). He only used a single oidc, but seemed to want another cookie.
https://stackoverflow.com/questions/78533634/asp-net-core-blazor-multiple-authentication-schemes-oidc-custom-cookies
dotnet info output:

Contributor guide
Assessment
This issue has not been assessed yet.