14

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
  • 44,147
  • 19
  • 125
  • 140
Ahmed Magdy
  • 486
  • 5
  • 16
  • 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

24

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>();
seangwright
  • 15,728
  • 6
  • 40
  • 51
Kévin Chalet
  • 36,739
  • 7
  • 114
  • 128
  • 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
9

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
  • 8,102
  • 3
  • 34
  • 54