Search code examples
c#.netasp.net-core-2.0aspnetboilerplate

Implementing AbpIdentity cookies and JwtBearer


I inherited an ASPnetzero application that uses both a Web API and a MVC front-end. The API authenticated via Bearer and the front-end via AbpIdentity (Cookies). A couple of days ago I got brave and decided to update my nuGet packages. The update came with an upgrade from .netCore v1 to v2. But I had some difficulties with the authentication after the JwtBearer middleware became obsolete. I could authenticate using the cookies, but not using Bearer Tokens.

I tried almost everything. Using multiple authentication methods meant that only one worked at a time.

In Startup.cs I had the following (snippets):

public IServiceProvider ConfigureServices(IServiceCollection services)
        {
            services.AddAbpIdentity<Tenant, User, Role>()
            .AddUserManager<UserManager>()
            .AddRoleManager<RoleManager>()
            .AddSignInManager<SignInManager>()
            .AddClaimsPrincipalFactory<UserClaimsPrincipalFactory>()
            .AddDefaultTokenProviders();
        }
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
        {
          AuthConfigurer.Configure(app, _appConfiguration);
         }

This is however a self-answer question and I'm hoping that I'll help anyone with similar or equal cases, since I've put together my own solution. The idea was to get the application working with a Bearer token (only when using the API) and cookies (only when using the MVC).

I also had a challenge as the MVC did XHR calls to the API to get data to display on the front-end. This meant that the API also needed to work with Cookies (but only for MVC users).


Solution

  • So I finally figured it out and it required quite a bit of transformation. The result was that:

    1. API users only authenticated with a Bearer Token
    2. MVC users authenticated with Cookies and the same authentication was used for the API calls in the application after they logged in.

    All of the changes were made in Startup.cs, and I also commented out the reference to the AuthConfigure.cs file, which is now obsolete. I am open to any improvements or suggestions to the solution.

    The important pieces in the Startup.cs file:

    public IServiceProvider ConfigureServices(IServiceCollection services)
            {
              services.AddAuthentication()
                    .AddJwtBearer(cfg =>
                    {
                        cfg.RequireHttpsMetadata = false;
                        cfg.SaveToken = true;
    
                        cfg.TokenValidationParameters = new TokenValidationParameters()
                        {
                            // The application URL is used for the Issuer and Audience and is included in the appsettings.json
                            ValidIssuer = _appConfiguration["Authentication:JwtBearer:Issuer"],
                            ValidAudience = _appConfiguration["Authentication:JwtBearer:Audience"],
                            IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_appConfiguration["Authentication:JwtBearer:SecurityKey"]))
                        };
    
                    });
    
                 // Activate Cookie Authentication without Identity, since Abp already implements Identity below.
                 services.ConfigureApplicationCookie(options => options.LoginPath = "/Account/Login");
    
                 // Add the Authentication Scheme Provider which will set the authentication method based on the kind of request. i.e API or MVC
                services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
                services.AddSingleton<IAuthenticationSchemeProvider, CustomAuthenticationSchemeProvider>();
    
                 // Some of these extensions changed
                 services.AddAbpIdentity<Tenant, User, Role>()
                .AddUserManager<UserManager>()
                .AddRoleManager<RoleManager>()
                .AddSignInManager<SignInManager>()
                .AddClaimsPrincipalFactory<UserClaimsPrincipalFactory>()
                .AddDefaultTokenProviders();
    //…
    }
    
    public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
            {
    // app.UseAuthentication is critical here
                app.UseAuthentication();
                app.UseAbp(); //Initializes ABP framework.
                app.UseCors("CorsPolicy");
    //…
     //AuthConfigurer.Configure(app, _appConfiguration);
    //…
             }
    }
    
    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.
    
            var authHeader = httpContextAccessor.HttpContext.Request.Headers["Authorization"].FirstOrDefault();
            if (authHeader?.StartsWith("Bearer ") == true)
            {
                return await GetSchemeAsync(JwtBearerDefaults.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();
    }