Search code examples
c#mongodbasp.net-identitymongodb-.net-driver

Authorize not working for roles using ASP.NET Core 3.1 Identity with MongoDB


Update:It is not just the Admin role which is not working - it seems any route which required authorisation is returning a 401.

I want to create an admin role to control access to my AdminController. My stack is MongoDb/.NET Core(3.1) for the API/ Angular 9 front end.

I seed my database with the roles

        private static void SeedRoles(RoleManager<MongoRole> roleManager)
        {
            if (!roleManager.RoleExistsAsync("User").Result)
            {
                MongoRole role = new MongoRole();
                role.Name = "User";
                IdentityResult roleResult = roleManager.
                CreateAsync(role).Result;
            }


            if (!roleManager.RoleExistsAsync("Admin").Result)
            {
                MongoRole role = new MongoRole();
                role.Name = "Admin";
                IdentityResult roleResult = roleManager.
                CreateAsync(role).Result;
            }
        }

In another seed method I've added the following 2 roles to my user account

                    userManager.AddToRoleAsync(user, "User").Wait();
                    userManager.AddToRoleAsync(user, "Admin").Wait();

In my startup file I've configured my mongo identity provider

services.AddIdentityMongoDbProvider<AspNetCore.Identity.Mongo.Model.MongoUser, AspNetCore.Identity.Mongo.Model.MongoRole>(identityOptions =>
            {
                identityOptions.Password.RequiredLength = 6;
                identityOptions.Password.RequireLowercase = false;
                identityOptions.Password.RequireUppercase = false;
                identityOptions.Password.RequireNonAlphanumeric = false;
                identityOptions.Password.RequireDigit = false;
            }, mongoIdentityOptions => {
                mongoIdentityOptions.ConnectionString = **REMOVED CONN STR FROM HERE**;
            });

My login method in the user controller gets the roles from my user and adds them to a claim list - as far as I'm aware this is so it can be included within the token which can be checked for roles. When I debugged and added a break point on this method it was clear that the roles are being added to the claimList - so I'm not sure the problem is there.

 // POST api/user/login
        [HttpPost]
        [AllowAnonymous]
        public async Task<ActionResult> Login([FromBody]LoginEntity model)
        {
            if (ModelState.IsValid)
            {
                var result = await _signInManager.PasswordSignInAsync(model.UserName, model.Password, false, false);
                if (result.Succeeded)
                {
                    string key = model.UserName + "ezgig321";
                    var appUser = _userManager.Users.SingleOrDefault(r => r.UserName == model.UserName);
                    var issuer = "ezgig";
                    var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(key));
                    var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);
                    var roles = await _userManager.GetRolesAsync(appUser);
                    var claimList = new List<Claim>();
                    foreach (var role in roles)
                    {
                        var roleClaim = new Claim(ClaimTypes.Role, role);
                        claimList.Add(roleClaim);
                    }
                    claimList.Add(new Claim("username", model.UserName));

                    //var token = AuthenticationHelper.GenerateJwtToken(model.Email, appUser, _configuration);
                    var token = new JwtSecurityToken(issuer, //Issure    
                                    issuer,  //Audience    
                                    claimList,
                                    expires: DateTime.Now.AddDays(1),
                                    signingCredentials: credentials);

                    var encodedJwt = new JwtSecurityTokenHandler().WriteToken(token);

                    var rootData = new LoginResponse(encodedJwt, appUser.UserName);
                    return Ok(rootData);
                }
                return StatusCode((int)HttpStatusCode.Unauthorized, "Bad Credentials");
            }
            string errorMessage = string.Join(", ", ModelState.Values.SelectMany(x => x.Errors).Select(x => x.ErrorMessage));
            return BadRequest(errorMessage ?? "Bad Request");
        }

Yet when I use the JWT returned from logging in to my account with the admin role - I still get a 401 unauthorised when I'm trying to access this test method I've put in my admin controller.

    [Authorize(Roles ="Admin")]
    [Route("api/[controller]/[action]")]
    public class AdminController : Controller
    {
        // GET api/admin/admintest
        [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
        [HttpGet]
        public  ActionResult AdminTest()
        {
            return Ok("you seem to have admin authorisation");
        }

Getting a 401 using postman


Solution

  • It turns out it was just stupidity which caused this error. I'd hardcoded in the issuer and JWT key variables of the Register/Login end point, and I'd wrote them in incorrectly.

    Because they then didn't match with the issuer/jwt key in the startup.cs file(see below)...

    
    services.AddAuthentication(options =>
                {
                    //Set default Authentication Schema as Bearer
                    options.DefaultAuthenticateScheme =
                               JwtBearerDefaults.AuthenticationScheme;
                    options.DefaultScheme =
                               JwtBearerDefaults.AuthenticationScheme;
                    options.DefaultChallengeScheme =
                               JwtBearerDefaults.AuthenticationScheme;
                }).AddJwtBearer(cfg =>
                {
                    cfg.RequireHttpsMetadata = false;
                    cfg.SaveToken = true;
                    cfg.TokenValidationParameters =
                           new TokenValidationParameters
                           {
                               ValidIssuer = Configuration["JwtIssuer"],
                               ValidAudience = Configuration["JwtIssuer"],
                               IssuerSigningKey =
                            new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["JwtKey"])),
                               ClockSkew = TimeSpan.Zero // remove delay of token when expire
                           };
                });
    

    This meant that the JWT keys were being rejected as invalid. Sorry if anyone wasted any time on this.