Asp.net core 2.0+ - Multiple Authentication Schemes (Cookie / Bearer)
Asked Answered
N

1

5

I've been struggling to get multiple authentication schemes working correctly in Asp.net core 2.1.

I am using Identity Server with an implicit flow and OpenIdConnect as the protocol.

When authorizing an action or controller with one of the schemes only (e.g Cookie or Bearer) the functionality works correctly.

Example:

  [Authorize(AuthenticationSchemes = "Cookies")]
  [Route("Cookies")]
  public class BearerAndCookiesController : Controller {

If I however I specify on the Authorize attribute both schemes, then it fails partially. Bearer works as normal, but when I try to view the page in the browser it attempts to redirect to a local Login page (http://localhost/Account/Login).

When I inspect the debug logs of Identity Server nothing is returned, which makes sense as it hasn't attempted to contact the Authority. However when I look at the debug log of the Test MVC Site both the Bearer and Cookie schemes are challenged:

Microsoft.AspNetCore.Hosting.Internal.WebHost:Information: Request starting HTTP/1.1 GET http://localhost:5002/cookies  
Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker:Information: Route matched with {action = "Get", controller = "BearerAndCookies"}. Executing action MvcClient.Controllers.BearerAndCookiesController.Get (MvcClient)
Microsoft.AspNetCore.Authorization.DefaultAuthorizationService:Information: Authorization failed.
Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker:Information: Authorization failed for the request at filter 'Microsoft.AspNetCore.Mvc.Authorization.AuthorizeFilter'.
Microsoft.AspNetCore.Mvc.ChallengeResult:Information: Executing ChallengeResult with authentication schemes (Bearer, Cookies).
Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerHandler:Information: AuthenticationScheme: Bearer was challenged.
Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationHandler:Information: AuthenticationScheme: Cookies was challenged.
Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker:Information: Executed action MvcClient.Controllers.BearerAndCookiesController.Get (MvcClient) in 68.1922ms
Microsoft.AspNetCore.Hosting.Internal.WebHost:Information: Request finished in 93.2016ms 302 
Microsoft.AspNetCore.Hosting.Internal.WebHost:Information: Request starting HTTP/1.1 GET http://localhost:5002/Account/Login?ReturnUrl=%2Fcookies  
Microsoft.AspNetCore.Hosting.Internal.WebHost:Information: Request finished in 30.2532ms 404 
Failed to load resource: the server responded with a status of 404 (Not Found) [http://localhost:5002/Account/Login?ReturnUrl=%2Fcookies]

Does anyone know why this isn't working? I'll the person a beer! It's been hunting me for the last week.

https://learn.microsoft.com/en-us/aspnet/core/security/authorization/limitingidentitybyscheme?view=aspnetcore-2.2&tabs=aspnetcore2x

Here is my Startup.cs configuration:

   public void ConfigureServices(IServiceCollection services) {

      services.AddMvc();

      JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();

      services.AddAuthentication(options => {
        options.DefaultScheme = "Cookies";
        options.DefaultChallengeScheme = "oidc";
      })
      .AddJwtBearer(options => {
        options.Authority = "http://localhost:5000";
        options.Audience = "myApi";
        options.RequireHttpsMetadata = false;
      })
      .AddCookie("Cookies")
      .AddOpenIdConnect("oidc", options => {
        options.Authority = "http://localhost:5000";
        options.RequireHttpsMetadata = false;

        options.ClientId = "myApi";
        options.SaveTokens = true;
      });
    }
  [Authorize(AuthenticationSchemes = AuthSchemes)]
  [Route("Cookies")]
  public class BearerAndCookiesController : Controller {

    private const string AuthSchemes =
      JwtBearerDefaults.AuthenticationScheme + "," +
      CookieAuthenticationDefaults.AuthenticationScheme;
Nurmi answered 20/7, 2019 at 19:56 Comment(0)
N
6

I wanted to give a better explanation of this answer:

  1. I had to move services.AddAuthorization after the part were I added both of the schemes. This ensures both schemes are registered correctly.
 JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();

      services.AddAuthentication(options => {
        options.DefaultScheme = "Cookies";
        options.DefaultChallengeScheme = "oidc";
      })
        .AddCookie("Cookies")
        .AddOpenIdConnect("oidc", options => {
          options.SignInScheme = "Cookies";
          options.Authority = "http://localhost:5000";
          options.RequireHttpsMetadata = false;
          options.ClientId = "myApi";
          options.SaveTokens = true;
        }).AddIdentityServerAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme, options => {
          options.Authority = "http://localhost:5000";
          options.ApiName = "myApi";
          options.RequireHttpsMetadata = false;
        });

      services.AddAuthorization(options => {
      ...
      });
  1. Then instead of specifying the Authorization Scheme as a part of the Controller Action Authorize tag, I used a global policy when using services.AddAuthorization
services.AddAuthorization(options =>
{
    var defaultAuthorizationPolicyBuilder = new AuthorizationPolicyBuilder(
        CookieAuthenticationDefaults.AuthenticationScheme,
        JwtBearerDefaults.AuthenticationScheme);
    defaultAuthorizationPolicyBuilder =
        defaultAuthorizationPolicyBuilder.RequireAuthenticatedUser();
    options.DefaultPolicy = defaultAuthorizationPolicyBuilder.Build();
});
  1. When I navigated to the any parts of the API it wouldn't redirect to the Login screen. I noticed that if you logged in first by navigating to Identity Server, then go back to that page it would actually authenticate you as normal. So I've put in what feel to be a little bit of hack. It is important that this directly goes in under the app.UseAuthentication.
  app.UseAuthentication();
      app.Use(async (context, next) => {
        await next();
        var bearerAuth = context.Request.Headers["Authorization"]
                           .FirstOrDefault()?.StartsWith("Bearer ") ?? false;
        if (context.Response.StatusCode == 401
            && !context.User.Identity.IsAuthenticated
            && !bearerAuth) {
          await context.ChallengeAsync("oidc");
        }
      });

Bob's your uncle... and thanks to this post for helping considerably!! oipapio.com/question-1510997

Nurmi answered 21/7, 2019 at 18:24 Comment(1)
This doesn't work when I use it in a combination with [Authorize] with any parameters e.g. [Authorize(Roles=...)] because DefaultPolicy doesn't apply in such case.Illicit

© 2022 - 2024 — McMap. All rights reserved.