1
votes

I've created an ASP.NET Core 3.1 application that uses 2 authentication types - cookie and JWT bearer.

I've setup a scheme that redirects users to the proper scheme based on the path requested:

.AddAuthentication(sharedOptions =>
{
    sharedOptions.DefaultScheme = "smart";
    sharedOptions.DefaultChallengeScheme = "smart";
})
.AddPolicyScheme("smart", "Bearer Authorization or Cookie", options =>
{
    options.ForwardDefaultSelector = context =>
    {
        var requestPath = context.Request.Path;

        if (CookiePolicyPathRegex.IsMatch(requestPath))
        {
            return CookieAuthenticationDefaults.AuthenticationScheme;
        }

        return JwtBearerDefaults.AuthenticationScheme;
    };
})
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme)
.AddOAuthServiceScheme(Configuration); // Custom handler for JWT

I setup the authorization policies like so:

options.AddPolicy(ApiPolicies.CookiePolicy, policy =>
{
    // policy.AddAuthenticationSchemes(CookieAuthenticationDefaults.AuthenticationScheme);
    policy.RequireAuthenticatedUser();
    policy.RequireRole(Roles.Access);
});

options.AddPolicy(ApiPolicies.JwtPolicy, policy =>
{
    policy.AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme);
    policy.RequireAuthenticatedUser();
});

This works fine, the proper policies are being triggered, but I have one problem. In my integration tests I use a middleware that adds the ClaimsIdentity for the cookie authentication:

public async Task Invoke(HttpContext context)
{
    //  Removed for brevity

    var claimsIdentity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);

    context.User = new ClaimsPrincipal(claimsIdentity);

    await _next(context);
}

The middleware is setup to run before the Auth middlewares

ConfigureAdditionalMiddleware(app);

app.UseAuthentication();
app.UseAuthorization();

If I uncomment the // policy.AddAuthenticationSchemes(CookieAuthenticationDefaults.AuthenticationScheme); part from the cookie policy, the the authorization part doesn't see the Identity created in the middleware. If I leave it commented, the Identity is there, with claims, authentication type and everything. If I look in the PolicyScheme that forwards to the two auth schemes, the Identity is there.

My question is, why does adding CookieAuthenticationDefaults.AuthenticationScheme somehow hide the User Identity that was created with the same authentication type?

1
Is it possible you place your middleware after UseAuthentication middleware so identity isn't set yet? - Alexander
Good point, I edited the post to include that part of Startup.cs, but the way I see it it should be fine. The Identity is visible in the scheme that forwards to the auth schemes - Tamás Szabó
Identity can be set in both UseAuthentication and in UseAuthorization, see this post to learn more about what exactly UseAuthentication does stackoverflow.com/questions/48836688/… - Michael Shterenberg

1 Answers

2
votes

The authorization middleware will evaluate your policy and will run the authentication logic which will override the user.Context

Here is the relevant code snippet (I removed and simplified the code to highlight the relevant parts):

public virtual async Task<AuthenticateResult> AuthenticateAsync(AuthorizationPolicy policy, HttpContext context)
{
    if (policy.AuthenticationSchemes != null && policy.AuthenticationSchemes.Count > 0)
    {
        var newPrincipal = await context.AuthenticateAsync(scheme).Principal;

        if (newPrincipal != null)
        {
            context.User = newPrincipal;
            return AuthenticateResult.Success(new AuthenticationTicket(newPrincipal, string.Join(";", policy.AuthenticationSchemes)));
        }
        else
        {
            context.User = new ClaimsPrincipal(new ClaimsIdentity());
            return AuthenticateResult.NoResult();
        }
    }
    ...
}

So, as you can see, when you define a scheme for your policy, then you enter the "if" statement (which will set a new context.User, and if you comment the line, the authentication logic will not run and your custom user object will be there