Search code examples
authenticationasp.net-coreasp.net-core-mvc

How to ConfigureServices Authentication based on routes in ASP.NET Core 2.0


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());

Solution

  • 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>();