dotnet / dotnet/aspnetcore

Net 8 Blazor Web App (Interactive server w/ prerendering ) - multiple schemes doesn't work

Open
#57,023 9 comments 0 reactions 0 assignees View on GitHub
area-security Docs
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
![image](https://github.com/user-attachments/assets/b0af60ca-314c-4338-81a6-bfa2005649e0)

### Routes.Razor:
- Has AuthorizeRouteView
![image](https://github.com/user-attachments/assets/c7a693d8-f186-459c-9231-9fe66e8bf753)

### App.Razor:

![image](https://github.com/user-attachments/assets/c2d02204-b04d-404b-9a0f-fcec35c1e29a)


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:
![image](https://github.com/user-attachments/assets/04b972e3-7def-46e9-99e1-243d2b2982c9)

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.