In .NET Core 1 we could use
app.UseCookieAuthentication(new CookieAuthenticationOptions()
{
AutomaticAuthenticate = true,
...
to ensure the cookie was evaluated first. On top of this we had either OAuth2 or a custom authentication module using the Basic authentication header. This made the following flow trivial:
- User reach website, gets a WWW-Authorize challenge (Bearer or Basic depending on settings)
- User responds to the challenge (so for Basic, provide username/password)
- As soon as the relevant middleware would validate the Bearer or Basic token on the next request, it would sign in the user - and as a result the cookie middleware would add its cookie to the respond.
- On the next request the incoming cookie would be validated. If it is still valid, the user is signed in and as a result the following OAuth2 or custom middleware simply backs off and do not perform any action.
We are now trying to update that to ASP.NET Core 6 (or whatever it is called these days).
In the event handler of our custom AuthenticationHandler the following code:
context.HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, context.Principal)
does result in the cookies being set.
With
services.AddAuthorization(options =>
{
options.DefaultPolicy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.AddAuthenticationSchemes(CookieAuthenticationDefaults.AuthenticationScheme, "MyCustomScheme")
.Build();
});
it will first validate the cookie, but it will always run "MyCustomScheme" as well. In some environments validating a username/password can add 30 seconds due to timeouts in their way too complicated infrastructure setup, and I do not see a way for my custom handler to access any previous identities allowing it to shortcut (so no, "oh, already validated by cookie, so I do not need to do anything" unless I start seriously hacking).
I could also write a "ForwardDefaultSelector" (from https://docs.microsoft.com/en-us/aspnet/core/security/authorization/limitingidentitybyscheme?view=aspnetcore-6.0)
options.ForwardDefaultSelector = context =>
{
string authorization = context.Request.Headers[HeaderNames.Authorization];
if (!string.IsNullOrEmpty(authorization) && authorization.StartsWith("Bearer "))
{
var token = authorization.Substring("Bearer ".Length).Trim();
var jwtHandler = new JwtSecurityTokenHandler();
return (jwtHandler.CanReadToken(token) && jwtHandler.ReadJwtToken(token).Issuer.Equals("B2C-Authority"))
? "B2C" : "AAD";
}
return "AAD";
};
but checking the cookie exists is of course not enough - it might be present but expired in which case setting the only authentication schema to be the cookie would result in a failed login - even if the correct token is being passed in as well.
Am I missing some obvious way to get the cookie to authenticate if able, but if not able let another handler have a go? It seems like a simple task: Try to authenticate with Cookie, if fail, try X. 2 lines of code if I could just find a place to put those lines.
And yes, I am aware we should skip this and only support OAuth2. Unfortunately the world moves slow.