Dotnet core 2.0 authentication multiple schemas identity cookies and jwt

didiHamman picture didiHamman · Aug 20, 2017 · Viewed 16.1k times · Source

In dotnet core 1.1 asp, I was able to configure and use identity middleware followed by jwt middleware by doing the following:

  app.UseIdentity();
  app.UseJwtBearerAuthentication(new JwtBearerOptions() {});

This has now changed in that we implement the middleware with:

   app.UseAuthentication();

Configuration of the settings is done via the ConfigureServices section of Startup.cs.

There are some references to the use of authorization schema's in the migration documentation:

https://docs.microsoft.com/en-us/aspnet/core/migration/1x-to-2x/identity-2x#authentication-middleware-and-services

In 2.0 projects, authentication is configured via services. Each authentication scheme is registered in the ConfigureServices method of Startup.cs. The UseIdentity method is replaced with UseAuthentication.

Additionally there is a reference to:

Setting Default Authentication Schemes

In 1.x, the AutomaticAuthenticate and AutomaticChallenge properties were intended to be set on a single authentication scheme. There was no good way to enforce this.

In 2.0, these two properties have been removed as flags on the individual AuthenticationOptions instance and have moved into the base AuthenticationOptions class. The properties can be configured in the AddAuthentication method call within the ConfigureServices method of Startup.cs:

Alternatively, use an overloaded version of the AddAuthentication method to set more than one property. In the following overloaded method example, the default scheme is set to CookieAuthenticationDefaults.AuthenticationScheme. The authentication scheme may alternatively be specified within your individual [Authorize] attributes or authorization policies.

Is it still possible in dotnet core 2.0 to use multiple authentication schemas? I cannot get the policy to respect the JWT configuration ("Bearer" schema), and only Identity is working at present with both configured. I can't find any samples of multiple authentication schemas.

Edit:

I've reread the documentation, and now understand that the:

app.UseAuthentication()

adds automatic authentication against a default schema. Identity configures the default schemas for you.

I have gotten around the issue with what seems like a hack working against the new api's by doing the following in Startup.cs Configure:

    app.UseAuthentication();
    app.Use(async (context, next) =>
    {
        if (!context.User.Identity.IsAuthenticated)
        {
            var result = await context.AuthenticateAsync(JwtBearerDefaults.AuthenticationScheme);
            if (result?.Principal != null)
            {
                context.User = result.Principal;
            }
        }

        await next.Invoke();
    });

Is this the correct way to do this, or should I be utilising the framework, DI and interfaces for custom implementations of IAuthenticationSchemeProvider?

Edit - Futher details of the implementation and where to find it.

The JWT Config can be found here, and I am using policies to define the authorization, which include the accepted auth schema's:

https://github.com/Arragro/ArragroCMS/blob/master/src/ArragroCMS.Management/Startup.cs

Custom middleware is still implemented. The Auth controller is here:

https://github.com/Arragro/ArragroCMS/blob/master/src/ArragroCMS.Web.Management/ApiControllers/AuthController.cs

It uses API Keys generated by the app to get read only access to data. You can find the implementation of a controller utilising the policy here:

https://github.com/Arragro/ArragroCMS/blob/master/src/ArragroCMS.Web.Management/ApiControllers/SitemapController.cs

Change the DB Connection string to point to your SQL Server, and run the application. It migrates the DB automatically and configures an admin user ([email protected] - ArragroPassword1!). Then go to the Settings tab in the menu bar and click "Configure the JWT ReadOnly API Key Settings" to get a key. In postman, get a jwt token by configuring a new tab and setting it to POST with the following address:

http://localhost:5000/api/auth/readonly-token

Supply the headers: Content-Type: application/json

Supply the body:

{
    "apiKey": "the api token from the previous step"
}

Copy the token in the response, and then use the following in postman:

http://localhost:5000/api/sitemap/flat

Authorization: "bearer - The token you received in the previous request"

It will work inititally because of the custom middleware. Comment out the code mentioned above and try again and you will receive a 401.

Edit -@DonnyTian's answer below covers my solution in his comments. The problem I was having was setting a default policy on UseMvc, but not supplying the schema's:

    services.AddMvc(config =>
    {
        var defaultPolicy = new AuthorizationPolicyBuilder(new[] { JwtBearerDefaults.AuthenticationScheme, IdentityConstants.ApplicationScheme })
                         .RequireAuthenticatedUser()
                         .Build();
        config.Filters.Add(new AuthorizeFilter(defaultPolicy));
        config.Filters.Add(new AutoValidateAntiforgeryTokenAttribute());
        config.Filters.Add(new ValidateModelAttribute());
    });

Following the advice, this works without custom middleware.

Answer

DonnyTian picture DonnyTian · Aug 24, 2017

Asp.Net Core 2.0 definitely support multiple authentication schemes. Rather than a hacking with authenticate middleware, you can try to specify the schema in Authorize attribute:

[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]

I gave a try and it worked fine. Assuming you have added both Identity and JWT as below:

services.AddIdentity<ApplicationUser, ApplicationRole>()
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)

Since AddIdentity() already set cookie authentication as the default schema, we have to specify schema in Authorize attribute of controllers. For now, I have no idea how to overwrite the default schema set by AddIdentity(), or maybe we'd better not to do that.

A work around is to compose a new class (you can call it JwtAuthorize) that derives from Authorize and have Bearer as the default schema, so you don't have to specify it every time.

UPDATE

Found the way to override Identity default authentication scheme!

Instead of below line:

services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)

Use below overload to set default schema:

services.AddAuthentication(option =>
                {
                    option.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                })
                .AddJwtBearer(options =>....

UPDATE 2 As mentioned in comments, you can enable both Identity and JWT auth by join them together. [Authorize(AuthenticationSchemes = "Identity.Application" + "," + JwtBearerDefaults.AuthenticationScheme)]