Search code examples
asp.net-coreasp.net-core-webapiasp.net-authorizationjwt

Authorization does not work after adding JWT token in ASP .Net Core 2.2


I'm learning how to use JWT token authentication in ASP. I have a simple website with CRUD operations and a repository, I added register/login functionality and I have a single role called "Administrator" who is a superuser. Usually, this is how I check if the user currently logged in in an admin or not:

bool admin = User.IsInRole(Constants.ADMIN_ROLE);

This feature was working fine until I added JWT authentication to my app. Now, logging in with my admin account (added to the db manually), I never get the correct value. When I try to query the user I get NULL:

var user = await _userManager.GetUserAsync(HttpContext.User)

This is my ConfigureServices in Startup.cs:

public void ConfigureServices(IServiceCollection services)
    {
        services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);

        services.AddIdentity<MyUser, MyRole>()
            .AddEntityFrameworkStores<MyContext>()
            .AddRoles<MyRole>();


        services.AddDbContext<MyContext>(builder =>
        {
            builder.UseSqlServer(Configuration["ConnectionStrings"]);
        });

        // repo code here ommitted for clarity

        var appSettingsSection = Configuration.GetSection("AppSettings");
        services.Configure<AppSettings>(appSettingsSection);

        var appSettings = appSettingsSection.Get<AppSettings>();
        var key = Encoding.ASCII.GetBytes(appSettings.Secret);
        services.AddAuthentication(x =>
        {
            x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
            x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
        })
        .AddJwtBearer(x =>
        {
            x.RequireHttpsMetadata = false;
            x.SaveToken = true;
            x.TokenValidationParameters = new TokenValidationParameters
            {
                ValidateIssuerSigningKey = true,
                IssuerSigningKey = new SymmetricSecurityKey(key),
                ValidateIssuer = false,
                ValidateAudience = false,
                ValidateLifetime = true,
            };

        });

    }

And this is my Configure class:

public void Configure(IApplicationBuilder app, IHostingEnvironment env, IServiceProvider serviceProvider)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }
        else
        {
            // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
            app.UseHsts();
        }

        app.UseHttpsRedirection();
        app.UseAuthentication();
        app.UseMvc();
        app.UseCors(x => x.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader());
    }

It was ny understanding that since JWT is authentication it should not affect authorization at all, so I'm thinking I forgot to use some additional options, but I can't seem to find what. Any help is appreciated!


Solution

  • I ended up implementing authentication in JWT entirely.

    I added a new user claim inside my token during the creation of the token descriptor:

                      ... 
     Subject = new ClaimsIdentity(new Claim[]
                {
                    new Claim(ClaimTypes.Name, userId.ToString()),
                    new Claim(ClaimTypes.Role, roleId)
                }),
                      ...
    

    and adding info related to the role inside (in my case, I used the ID of the role to make it less descriptive should anyone manage to decipher it)

    var roles = await _userManager.GetRolesAsync(userInDb);
    var userRoleId = await _roleManager.FindByNameAsync(roles.First());
                           ...
    return Ok(_tokenService.GenerateToken(userInDb.Id, userRoleId.Id.ToString()));
    

    Then, deciding if the user is admin or not is as simple as getting the appropriate value out of the token:

    private async Task<bool> IsUserAdmin()
    {
        var roleId = Guid.Parse(User.Claims.FirstOrDefault(e => e.Type == "role").Value);
        var adminId = (await _roleManager.FindByNameAsync(Constants.ADMIN_ROLE))?.Id;     
        return roleId == adminId;
    }
    

    Although this seems to work, I still think that there may be better (less hacky) ways to do this that invole better integrating JWT into identity, so if someone can propose a more concise solution, I am happy to accepted it as answer instead of my solution.