asp.net-corejwtasp.net-identityrole-based

Role Based authorization not working after JWT is added to .Net Identity


After adding Jwt authentication to the Web API, it seems okay if I do not specify roles in the Weather Forecast Controller method. After I specified Roles, only JWT authorisation works. I tried various things, transforming claims and others, but nothing helped. When .net identity authentication is used, it results in 401 Unauthorized.

[Full Source code][https://drive.google.com/file/d/1N57j7NYkH-d_pS3cYAEU_yTVZF47JqcV/view?usp=drive_link]

Startup Class


    public class Program
    {
        public static void Main(string[] args)
        {
            var builder = WebApplication.CreateBuilder(args);
            ConfigurationManager configuration = builder.Configuration;

            // Add services to the container.

            // For Entity Framework
            builder.Services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(configuration.GetConnectionString("ConnStr")));

            // For Identity
            builder.Services.AddIdentity<IdentityUser, IdentityRole>()
                .AddEntityFrameworkStores<ApplicationDbContext>()
                .AddClaimsPrincipalFactory<AppClaimsPrincipalFactory>()
                .AddDefaultTokenProviders();

            // Adding Authentication
            builder.Services.AddAuthentication(options =>
                {
                    options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                    options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
                    options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
                })

                // Adding Jwt Bearer
                .AddJwtBearer(options =>
                {
                    options.SaveToken = true;
                    options.RequireHttpsMetadata = false;
                    options.TokenValidationParameters = new TokenValidationParameters()
                    {
                        ValidateIssuer = true,
                        ValidateAudience = true,
                        ValidAudience = configuration["JWT:ValidAudience"],
                        ValidIssuer = configuration["JWT:ValidIssuer"],
                        IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(configuration["JWT:Secret"]))
                    };
                });

            builder.Services.ConfigureApplicationCookie(options =>
            {
                options.Events.OnRedirectToLogin = context =>
                {
                    context.Response.Headers["Location"] = context.RedirectUri;
                    context.Response.StatusCode = 401;
                    return Task.CompletedTask;
                };
            });

            builder.Services.AddAuthorization(options =>
            {
                options.DefaultPolicy = new AuthorizationPolicyBuilder("Identity.Application", JwtBearerDefaults.AuthenticationScheme)
                    
                    .RequireAuthenticatedUser()
                    .Build();
            });

            builder.Services.AddControllers();
            // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
            builder.Services.AddEndpointsApiExplorer();
            builder.Services.AddSwaggerGen();

            var app = builder.Build();

            // Configure the HTTP request pipeline.
            if (app.Environment.IsDevelopment())
            {
                app.UseSwagger();
                app.UseSwaggerUI();
            }

            app.UseRouting();
            app.UseHttpsRedirection();

            // Authentication & Authorization
            app.UseAuthentication();
            app.UseAuthorization();

            app.MapControllers();

            app.Run();
        }
    }

Weather Forecast Controller


    [ApiController]
    [Route("[controller]")]
    public class WeatherForecastController : ControllerBase
    {
        private static readonly string[] Summaries = new[]
        {
        "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
    };

        private readonly ILogger<WeatherForecastController> _logger;

        public WeatherForecastController(ILogger<WeatherForecastController> logger)
        {
            _logger = logger;
        }

        [HttpGet, Authorize(Roles = "Admin")]
        public IEnumerable<WeatherForecast> Get()
        {
            var identity = HttpContext.User.Identity as ClaimsIdentity;

            return Enumerable.Range(1, 5).Select(index => new WeatherForecast
            {
                Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
                TemperatureC = Random.Shared.Next(-20, 55),
                Summary = Summaries[Random.Shared.Next(Summaries.Length)]
            })
            .ToArray();
        }

    }

Solution

  • I tested your codes. The problem is you are using jwt authentication. Which require Authorize header with value Bearer ...token... in the request to pass the api.
    But on the other hand, you are tryinng to pass the authentication with asp.net identity cookie. "JWT" and "asp.net identity cookie" are different authentication schemes. Using this cookie will never pass an "JWT" protected api.
    This response means you have cookie but trying to pass it using "bearer"
    enter image description here
    An easy way to fix this is use this authorize attribute to specify the api use "asp.net identity" scheme

    [Authorize(AuthenticationSchemes = "Identity.Application", Roles = "Admin")]
    

    More over if you want this api can be pass by both "jwt" and "cookie". Modify like below:

                builder.Services.AddAuthorization(options =>
                {
                    options.AddPolicy("Jwt_Or_AspIdentiyCookie", policy =>
                    {
                        policy.AuthenticationSchemes.Add(JwtBearerDefaults.AuthenticationScheme);
                        policy.AuthenticationSchemes.Add("Identity.Application");
                        policy.RequireAuthenticatedUser();
                    });
                });
    

    Then in the controller use

            [Authorize(policy: "Jwt_Or_AspIdentiyCookie", Roles = "Admin")]