Search code examples
c#asp.net-coreidentityserver4

Could set two authentications for one Api ? Identity server4


I build a identity server, and with a client two grant_types. hybrid and password.

Now, each of them is ok for get access token independently. if add thems in the same time. Just hybrid works. Password mode could get access token, but when accessed the api with bearer token. I was redirected to the hybrid login page.

 // add password authentication
 services.AddAuthentication("Bearer")
    .AddJwtBearer("Bearer", options =>
    {
         options.Authority = "http://localhost";
         options.RequireHttpsMetadata = false;

         options.Audience = "SlideCloudStorage";
     });
 // add hybrid authentication
 // todo add this information to configuration.
 services.AddAuthentication(
       options =>
       {
           options.DefaultScheme = "Cookies";
           options.DefaultChallengeScheme = "oidc";
       })
    .AddCookie("Cookies")
    .AddOpenIdConnect(
         "oidc",
         options =>
         {
             options.SignInScheme = "Cookies";
             options.Authority = "http://localhost";
             options.RequireHttpsMetadata = false;

             options.ClientId = "slide-cloud-storage";
             options.ClientSecret = "secret";
             options.ResponseType = "code id_token";

             options.SaveTokens = true;
             options.GetClaimsFromUserInfoEndpoint = true;

             options.Scope.Add("offline_access");
             options.ClaimActions.MapJsonKey("website", "website");
           });

Hope this is not a XY problem. Why I want add two authentications?

Password mode is for my desktop client. Hybrid is for my web/mobile client.


Solution

  • services.AddAuthentication("Bearer")
    

    This sets the default authentication scheme to Bearer.

    services.AddAuthentication(
        options =>
        {
            options.DefaultScheme = "Cookies";
            options.DefaultChallengeScheme = "oidc";
        })
    

    And this sets the default authentication scheme to Cookies (and the default challenge scheme to oidc). So after this, the bearer authentication is no longer invoked by default.

    Configuring multiple authentication schemes is perfectly fine and in many cases also somewhat required. But you have to understand that there can only ever be a single default (for each authentication action) which will be invoked automatically by the framework. I’ve explained that in more detail for this question but the idea is basically that when you don’t do anything special, then the default authentication scheme will be used to authenticate the user when a request comes in.

    So in your case, with Cookies being the default scheme, users will be attempted to be signed in through their cookies. This is often a good default for user-centric applications since the cookie will be what most users use to authenticate themselves. On the other hand, the API access to those applications is more special and often limited to just a few controllers or routes; so having bearer authentication as a default is often not that useful.

    So when Cookies is being the default, and there can only be one default which will be invoked automatically, what is the way to use other schemes then? The answer to that are authorization policies.

    Authorization policies in general allow you to restrict access based on certain rules. A policy is basically a set of these rules. For authorization purposes, you will commonly check claim values to control whether someone is authorized to do something. There’s however one other aspect of authorization policies and that’s that they allow you to specify authentication schemes as rules.

    When you create an authorization policy using an AuthorizationPolicyBuilder, you can specify the authentication schemes it requires with it. When an authorization policy is then used to authorize requests, it will automatically authenticate those schemes if they haven’t been authenticated yet.

    So you can use this mechanism to trigger bearer authentication for your API controllers without affecting the default cookie authentication which will be used whenever you don’t specify anything else.

    [Authorize("ApiPolicy")]
    public class MyApiController : ControllerBase
    {
        // …
    }
    

    In Startup.ConfigureServices:

    services.AddAuthorization(options =>
    {
        var apiPolicy = new AuthorizationPolicyBuilder("Bearer")
            .RequireAuthenticatedUser()
            .Build();
        options.AddPolicy("ApiPolicy", apiPolicy);
    });
    

    With that, you configured a custom policy, which you can always extend to add additional requirements (such as special claims), and you are using that to authorize clients accessing your API.

    If you only need this for few actions or controllers, you can also specify the authentication schemes directly within the [Authorize] attribute. This saves you from having to create a custom authorization policy but on the other hand of course requires a bit more duplication and maintenance if you ever want to expand your policy with additional requirements (or changed authentication schemes):

    [Authorize(AuthenticationSchemes = "Bearer")] 
    public class MyApiController : ControllerBase
    {
        // …
    }
    

    Note that in the end, this will also result in a policy that will be created temporarily to authorize the user. So the effect and underlying mechanism is really the same.