16

In ASP.NET Core 1.x I could use authentication methods in Configure but now in ASP.NET Core 2.0 I have to set everything in ConfigureServices and can't configure it in Configure method. For example

public void ConfigureServices(IServiceCollection services)
{
    services.AddAuthentication()
            .AddCookie()
            .AddXX();
}

and then in

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    ....
    app.UseAuthentication();
}

in the past, I could use something like

app.UseOpenIdConnectAuthentication();

and I can't configure it anymore like this.

so how I can use something like this now in ASP.NET Core 2.0?

app.Map(new PathString("/MyPath"), i => i.UseMyAuthMethod());
Set
  • 47,577
  • 22
  • 132
  • 150
Ahmed Magdy
  • 506
  • 1
  • 5
  • 17
  • i found an answer from Microsoft guys on github https://github.com/aspnet/Security/issues/1479#issuecomment-360928524 – Ahmed Magdy Mar 15 '18 at 14:11

2 Answers2

28

In 2.0, the best option to do per-route authentication is to use a custom IAuthenticationSchemeProvider:

    public class CustomAuthenticationSchemeProvider : AuthenticationSchemeProvider
    {
        private readonly IHttpContextAccessor httpContextAccessor;
    
        public CustomAuthenticationSchemeProvider(
            IHttpContextAccessor httpContextAccessor,
            IOptions<AuthenticationOptions> options)
            : base(options)
        {
            this.httpContextAccessor = httpContextAccessor;
        }
    
        private async Task<AuthenticationScheme> GetRequestSchemeAsync()
        {
            var request = httpContextAccessor.HttpContext?.Request;
            if (request == null)
            {
                throw new ArgumentNullException("The HTTP request cannot be retrieved.");
            }
    
            // For API requests, use authentication tokens.
            if (request.Path.StartsWithSegments("/api"))
            {
                return await GetSchemeAsync(OAuthValidationDefaults.AuthenticationScheme);
            }
    
            // For the other requests, return null to let the base methods
            // decide what's the best scheme based on the default schemes
            // configured in the global authentication options.
            return null;
        }
    
        public override async Task<AuthenticationScheme> GetDefaultAuthenticateSchemeAsync() =>
            await GetRequestSchemeAsync() ??
            await base.GetDefaultAuthenticateSchemeAsync();
    
        public override async Task<AuthenticationScheme> GetDefaultChallengeSchemeAsync() =>
            await GetRequestSchemeAsync() ??
            await base.GetDefaultChallengeSchemeAsync();
    
        public override async Task<AuthenticationScheme> GetDefaultForbidSchemeAsync() =>
            await GetRequestSchemeAsync() ??
            await base.GetDefaultForbidSchemeAsync();
    
        public override async Task<AuthenticationScheme> GetDefaultSignInSchemeAsync() =>
            await GetRequestSchemeAsync() ??
            await base.GetDefaultSignInSchemeAsync();
    
        public override async Task<AuthenticationScheme> GetDefaultSignOutSchemeAsync() =>
            await GetRequestSchemeAsync() ??
            await base.GetDefaultSignOutSchemeAsync();
    }

Don't forget to register it in the DI container (ideally, as a singleton):

    // IHttpContextAccessor is not registered by default
    services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
    services.AddSingleton<IAuthenticationSchemeProvider, CustomAuthenticationSchemeProvider>();
Rami Assi
  • 910
  • 2
  • 10
  • 19
Kévin Chalet
  • 39,509
  • 7
  • 121
  • 131
  • Is there any documentation from Microsoft on this feature? It seems pretty handy; surprised I don't see anything on the docs site. We have different security requirements based on a unique customer name in the route. Being able to dynamically route people to the right authentication provider based on their configuration is pretty useful for that use-case. – Justin Helgerson Oct 24 '20 at 02:19
10

The Microsoft docs say what to do if you want to use multiple authentication schemes in ASP.NET Core 2+:

The following example enables dynamic selection of schemes on a per request basis. That is, how to mix cookies and API authentication:

public void ConfigureServices(IServiceCollection services)
{
    services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
        .AddCookie(options =>
        {
            // For example, can foward any requests that start with /api 
            // to the api scheme.
            options.ForwardDefaultSelector = ctx => 
               ctx.Request.Path.StartsWithSegments("/api") ? "Api" : null;
        })
        .AddYourApiAuth("Api");
}

Example:

I had to implement a mixed-authentication solution in which I needed Cookie authentication for some requests and Token authentication for other requests. Here is what it looks like for me:

services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
    .AddCookie(options =>
    {
        // if URL path starts with "/api" then use Bearer authentication instead
        options.ForwardDefaultSelector = httpContext => httpContext.Request.Path.StartsWithSegments("/api") ? JwtBearerDefaults.AuthenticationScheme : null;
    })
    .AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, o =>
        {
            o.TokenValidationParameters.ValidateIssuerSigningKey = true;
            o.TokenValidationParameters.IssuerSigningKey = symmetricKey;
            o.TokenValidationParameters.ValidAudience = JwtSignInHandler.TokenAudience;
            o.TokenValidationParameters.ValidIssuer = JwtSignInHandler.TokenIssuer;
        });

where the JWT Bearer authentication is implemented as described in this answer.

Tips:

One of the biggest 'gotchas' for me was this: Even though the Cookies Policy forwards requests with URLs that start with "/api" to the Bearer policy, the cookie-authenticated users can still access those URLs if you're using the [Authorize] annotation. If you want those URLs to only be accessed through Bearer authentication, you must use the [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] annotation on the API Controllers/Actions.

Josh Withee
  • 9,922
  • 3
  • 44
  • 62