Search code examples
c#authenticationasp.net-core-mvcjwtasp.net-identity

Using token-based together with cookie-based authentication without email


I'm working on .NET Core MVC project with Identity. I have a working project currently with normal cookie-based authentication, excerpt from Identity config:

public class IdentityHostingStartup : IHostingStartup
{
    public void Configure(IWebHostBuilder builder)
    {
        builder.ConfigureServices((context, services) => {
            services.AddDbContext<TauManagerIdentityDbContext>(options =>
                options.UseNpgsql(
                    context.Configuration.GetConnectionString("TauManagerIdentityDbContextConnection")));

            services.AddIdentity<ApplicationUser, IdentityRole>()
                .AddRoles<IdentityRole>()
                .AddRoleManager<ApplicationRoleManager>()
                .AddEntityFrameworkStores<TauManagerIdentityDbContext>()
                .AddUserManager<ApplicationIdentityUserManager>()
                .AddDefaultUI()
                .AddDefaultTokenProviders();
        });
    }
}

I'm using AuthorizeAttribute to control access of different roles to different actions in my web application.

Now I am now facing a situation where I have to use some kind of token-based authentication for one specific action only. I have read several articles and questions at SO about JWT setup with .NET Core Identity, the closest one I've found to my case is Using Identity with token and cookie authentication.

However, I have two questions:

  • Is this indeed the simplest method of generating auth tokens for such scenario?
  • All examples of JWT I've seen so far contain a reference to user's email, e.g. add new Claim(JwtRegisteredClaimNames.Sub, user.Email) to the list of claims when generating tokens. I do NOT collect users' emails at all, and this is a deliberate decision I'd like to keep. Is there any way to use e.g. username instead?

Thanks in advance!


Solution

  • Is this indeed the simplest method of generating auth tokens for such scenario?

    I assume this is in reference to the code in the question Using Identity with token and cookie authentication :

    var claims = new[]
    {
      new Claim(JwtRegisteredClaimNames.Sub, user.Email),
      new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
    };
    
    var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config["Tokens:Key"]));
    var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
    
    var token = new JwtSecurityToken(_config["Tokens:Issuer"],
      _config["Tokens:Issuer"],
      claims,
      expires: DateTime.Now.AddMinutes(30),
      signingCredentials: creds);
    

    When Creating a JWT the two piece of information you have to provide as a programmer are the Claims and how the Signature is created. In the previous code, the claims as the claims you need and the key and creds are required to create the signature. To create a full token, you use the JwtSecurityToken class to create the token. I don't know how this can be any simpler.

    Is there any way to use e.g. username instead?

    Claims are whatever you want them to be. The System.IdentityModel.Tokens.Jwt Namespace has few built-in claim names you can use from JwtRegisteredClaimNames Struct.

    So you could use one of the following:

    JwtRegisteredClaimNames.NameId
    JwtRegisteredClaimNames.Sid
    JwtRegisteredClaimNames.UniqueName
    

    or you can create your own.

    I think the underlying question is, how do I make sure my JWT authorizes automatically against ASP.Net Identity. If you're using cookies, you should be able to look at the ClaimsPrinciple and determine what claims are being used by Identity to Authenticate a request and put those same claims in your JWT.

    Update 1

    I'm currently only using JWT with Angular for a project. My code won't be complete because some of it will be specific to only using JWT/Bearer but it should help. I believe the most important part is the AddJwtBearer which if I'm not mistaken, will look for a bearer header valid/decode and populate the httpcontext.user with a ClaimsPrincipal with all the associated claims. These claims can be used with the AuthorizeAttribute to do authorization (claims and/or policies).

    StartUp.cs

        public void ConfigureServices(IServiceCollection services)
        {
            services
                .Configure<JwtIssuerOptions>(jwtIssuerOptionsConfig)
                .Configure<JwtIssuerOptions>(options =>
                {
                    options.SigningCredentials = new SigningCredentials(symmetricSecurityKey, SecurityAlgorithms.HmacSha512);
                });
    
            services
                .AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
                .AddJwtBearer(options =>
                {
                    options.IncludeErrorDetails = true;
                    options.TokenValidationParameters = new TokenValidationParameters
                    {
                        ClockSkew = TimeSpan.FromMinutes(5),
                        IssuerSigningKey = symmetricSecurityKey,
                        RequireSignedTokens = true,
                        RequireExpirationTime = true,
                        ValidateLifetime = true,
                        ValidAudience = jwtIssuerOptions.Audience,
                        ValidateIssuer = true,
                        ValidIssuer = jwtIssuerOptions.Issuer
                    };
                    if (_isDevelopment)
                    {
                        options.Events = new JwtBearerEvents
                        {
                            OnAuthenticationFailed = c =>
                            {
                                Debug.WriteLine(c.Exception.Message);
                                return Task.CompletedTask;
                            },
                        };
                    }
                });