Search code examples
c#asp.net-coreauthentication

Cannot call a method that is marked with the [Authorize (Roles = "role Name")] attribute. ASP.NET Core


So, I did a simple Jwt token authorization. When there were no roles, everything worked. However, when I tried to make the method accessible only by role, it displays 403 and other access errors.

My Program.cs:

        var builder = WebApplication.CreateBuilder(args);

        var Origins = "restaraunt-react-ap";

        builder.Services.AddTransient<ICheckLoginService, CheckLoginService>();
        builder.Services.AddTransient<IRestaurantTPDbContext, RestaurantTPDbContext>();
        builder.Services.AddTransient<IJWTService, JWTService>();
        builder.Services.AddTransient<IRoleService, RoleService>();

        builder.Services.AddDefaultIdentity<IdentityUser>().AddRoles<IdentityRole>().AddEntityFrameworkStores<RestarauntTPDBIdentityDBContext>();

        builder.Services.AddAuthorization(option =>
        {
            option.AddPolicy("admin", policyBuilder => policyBuilder.RequireClaim("admin", "waiter", "cook"));
        });
        builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
            .AddJwtBearer(options =>
            {
                options.TokenValidationParameters = new TokenValidationParameters
                {
                    ValidateIssuer = true,
                    ValidIssuer = AuthOptions.ISSUER,
                    ValidateAudience = true,
                    ValidAudience = AuthOptions.AUDIENCE,
                    ValidateLifetime = true,
                    IssuerSigningKey = AuthOptions.GetSymmetricSecurityKey(),
                    ValidateIssuerSigningKey = true,
                };
            });

        builder.Services.AddControllers();
        builder.Services.AddEndpointsApiExplorer();
        builder.Services.AddHttpContextAccessor();

        builder.Services.AddSwaggerGen();
        builder.Services.AddCors(options =>
            options.AddPolicy(Origins, policy =>
            {                policy.WithOrigins("http://localhost:3000/").AllowAnyMethod().AllowAnyHeader().AllowAnyOrigin();
            }));

        builder.Services.AddDbContext<RestaurantTPDbContext>(options => options.UseSqlServer("Server = NANOMACHINE; Database = RestaurantTP; Trusted_Connection=True; TrustServerCertificate=true;"));
        builder.Services.AddDbContext<RestarauntTPDBIdentityDBContext>(options => options.UseSqlServer("Server = NANOMACHINE; Database = RestaurantTP; Trusted_Connection=True; TrustServerCertificate=true;"));

var app = builder.Build();

        if (app.Environment.IsDevelopment())
        {
            app.UseSwagger();
            app.UseSwaggerUI();
        }

        app.UseHttpsRedirection();

        app.UseAuthorization();
        app.UseCors(Origins);
app.MapControllers();

app.Run();

New DbContext:

public class RestarauntTPDBIdentityDBContext : IdentityDbContext<IdentityUser>
 {
     public DbSet<IdentityRole> roles { get; set; }

     public RestarauntTPDBIdentityDBContext(DbContextOptions<RestarauntTPDBIdentityDBContext> options) : base(options)
     {

     }

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
     {
         optionsBuilder.UseSqlServer("Server = NANOMACHINE; Database = RestaurantTP; Trusted_Connection=True; TrustServerCertificate=true;");
     }
 }

My RoleService

public class RoleService : IRoleService
{
    private readonly IServiceProvider _serviceProvider;

    public RoleService(IServiceProvider serviceProvider)
    { 
        _serviceProvider = serviceProvider;
    }

    public async Task SetRoles()
    {
        var roleManager = _serviceProvider.GetRequiredService<RoleManager<IdentityRole>>();

        string[] roleNames = { "administrator", "cook", "waiter" };

        foreach (var roleName in roleNames)
        {
            bool roleExists = await roleManager.RoleExistsAsync(roleName);
            if (!roleExists)
            {
                await roleManager.CreateAsync(new IdentityRole(roleName));
            }
        }
    }

}

Token generator

public class JWTService : IJWTService
{
    public readonly IRoleService _roleService;
    public JWTService(IRoleService roleService) 
    {
        _roleService = roleService;
    }
    public string GenerateToken(string name, string role)
    {

        var claims = new List<Claim> { new Claim(ClaimsIdentity.DefaultNameClaimType, name, ClaimsIdentity.DefaultRoleClaimType, role) };

        var jwt = new JwtSecurityToken(
            issuer: AuthOptions.ISSUER,
            audience: AuthOptions.AUDIENCE,
            claims: claims,
            expires: DateTime.Now.AddSeconds(20),
            signingCredentials: new SigningCredentials(AuthOptions.GetSymmetricSecurityKey(), SecurityAlgorithms.HmacSha256)
            );
  
        return new JwtSecurityTokenHandler().WriteToken(jwt);
    }

}

My controller

[Route("api/[controller]")]
[ApiController]
public class AuthenticationController : ControllerBase
{
    private readonly ICheckLoginService _checkLoginService;
    private readonly IRoleService _roleService;

    public AuthenticationController(ICheckLoginService checkLoginService, IRoleService roleService)
    {
        _checkLoginService = checkLoginService;
        _roleService = roleService;
    }
    
    [HttpGet]
    [Route("gettest")]
    public IActionResult GetData()
    {
        var data = new { Message = "Hello from ASP.NET Core Web API" };
        return Ok(data);
    }

    [HttpPost]
    [Route("sendData")]
    public async Task<IActionResult> TryLogin([FromBody] AutRequest autRequest)
    {
        await Task.Run(() => _roleService.SetRoles());

        var validate = _checkLoginService.Login(autRequest);

        return Ok(validate);
    }

    [HttpGet]
    [Authorize(Roles = "admin")]
    [Route("getSecretInfo")]
    public IActionResult GetSecretInfo()
    {
        return Ok("QWERTY");
    }

    public record AutRequest(string name, string password);
}

I created a new DBContext and registered the roles in the database (with my RoleService), however it didn't work :(

I don’t quite understand the role of the new DBContext, but the roles are registered there. I also checked that other requests to the server work, but it is the methods that require authorization AND a role that do not work.


Solution

  • public string GenerateToken(string name, string role)
    {
        var claims = new List<Claim> { new Claim(ClaimsIdentity.DefaultNameClaimType, name, ClaimsIdentity.DefaultRoleClaimType, role) };
    
        var jwt = new JwtSecurityToken(
            issuer: AuthOptions.ISSUER,
            audience: AuthOptions.AUDIENCE,
            claims: claims,
            expires: DateTime.Now.AddSeconds(20),
            signingCredentials: new SigningCredentials(AuthOptions.GetSymmetricSecurityKey(), SecurityAlgorithms.HmacSha256)
            );
    
        return new JwtSecurityTokenHandler().WriteToken(jwt);
    }
    

    In this method you are creating a list of claims containing only one claim of type ClaimsIdentity.DefaultNameClaimType using this constructor, which I believe is not doing what you expect it to do. Instead, you should have a separate claim for each piece of information you want to pass within the token.

    So, your method should look like the following:

    public string GenerateToken(string name, string role)
    {
        var claims = new List<Claim>
        {
            new Claim(ClaimsIdentity.DefaultNameClaimType, name),
            new Claim(ClaimsIdentity.DefaultRoleClaimType, role)
        };
    
        var jwt = new JwtSecurityToken(
            issuer: AuthOptions.ISSUER,
            audience: AuthOptions.AUDIENCE,
            claims: claims,
            expires: DateTime.Now.AddSeconds(20),
            signingCredentials: new SigningCredentials(AuthOptions.GetSymmetricSecurityKey(), SecurityAlgorithms.HmacSha256)
        );
    
        return new JwtSecurityTokenHandler().WriteToken(jwt);
    }
    

    After such modification you will indeed have the role claim written in your token and authorization should work properly.