I am currently working on implementing protected routes on a simple Web API. But I can't seem to figure out how JWT works really. I am currently successfully creating and sending back the Token with:
if (!ModelState.IsValid) return BadRequest(ModelState);
var foundUser = await _userManager.Users.FirstOrDefaultAsync(u => u.UserName == model.Username);
if (foundUser == null) return BadRequest("Invalid Username or Password");
var correctPassword = await _userManager.CheckPasswordAsync(foundUser, model.Password);
if (!correctPassword) return BadRequest("Invalid Username or Password");
var token = await _tokenServices.CreateToken(foundUser);
Response.Cookies.Append("auth_token", token, new CookieOptions
{
HttpOnly = true,
Secure = false,
SameSite = SameSiteMode.Strict,
Expires = DateTime.UtcNow.AddHours(1)
});
return Ok(new { message = "Logged In succesfully!" });
And the token itself gets created like so:
public async Task<string> CreateToken(Usuario usuario)
{
var claims = new List<Claim>
{
new Claim(JwtRegisteredClaimNames.Jti,usuario.Id),
new Claim(JwtRegisteredClaimNames.Sub, usuario.UserName!),
};
var userRoles = await _userManager.GetRolesAsync(usuario);
foreach (var role in userRoles)
{
claims.Add(new Claim(ClaimTypes.Role, role));
}
var creds = new SigningCredentials(_key, SecurityAlgorithms.Aes256CbcHmacSha512);
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(claims),
Expires = DateTime.UtcNow.AddHours(1),
SigningCredentials = creds,
Issuer = _config["JWT:Issuer"]
};
var tokenHandler = new JwtSecurityTokenHandler();
var token = tokenHandler.CreateToken(tokenDescriptor);
return tokenHandler.WriteToken(token);
}
I am trying to implement my Protected routes through the use of Attibuttes within the Controllers:
[HttpPost("create-new")]
[Authorize(Roles = "Admin")]
public async Task<IActionResult> Create([FromBody] CreateRolesRequestDto model)
{
if (await _roleManager.RoleExistsAsync(model.Role)) return BadRequest("That role already exists");
var newRole = await _roleManager.CreateAsync(new IdentityRole(model.Role.ToUpper()));
if (newRole == null) return BadRequest("Invalid Input Data");
return Ok(newRole);
}
}
Now I understand there is probably something else to be done since I am using JWT to handle my Security but what? What else do I need in order to have protected Routes?
In case necessary this is the data that is seeded and used for the Users and its roles:
builder.Entity<Persona>().HasData(
new Persona
{
Id = 1,
Nombre = "Ismael",
ApellidoPaterno = "Moron",
ApellidoMaterno = "Pedraza",
Carnet = "12597382",
Telefono = "75526864",
UsuarioId = "1"
}
);
var hasher = new PasswordHasher<Usuario>();
builder.Entity<Usuario>().HasData(
new Usuario
{
Id = "1",
UserName = "ismael",
Email = "[email protected]",
PasswordHash = hasher.HashPassword(null, "123456"),
PersonaId = 1
}
);
List<IdentityRole> roles = new List<IdentityRole>
{
new IdentityRole
{
Id= "1",
Name="Admin",
NormalizedName = "ADMIN"
},
new IdentityRole
{
Id= "2",
Name="User",
NormalizedName = "User"
}
};
builder.Entity<IdentityRole>().HasData(roles);
// Assign Admin Role to the User
builder.Entity<IdentityUserRole<string>>().HasData(
new IdentityUserRole<string>
{
UserId = "1", // The Id of the Usuario
RoleId = "1" // The Id of the Admin role
}
);
As well as the whole of my Program.cs configuration:
builder.Services.AddControllers().AddNewtonsoftJson(options => options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore);
builder.Services.AddDbContext<AppDbContext>(options => options.UseSqlServer(builder.Configuration.GetConnectionString("Default")));
builder.Services.AddIdentity<Usuario, IdentityRole>(options =>
{
options.Password.RequiredLength = 6;
options.Password.RequireUppercase = true;
options.Password.RequireLowercase = true;
options.Password.RequireDigit = true;
}).AddEntityFrameworkStores<AppDbContext>().AddDefaultTokenProviders();
builder.Services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidIssuer = builder.Configuration["JWT:Issuer"],
ValidateAudience = false,
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration["JWT:SecretKey"]!))
};
});
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("AdminPolicy", policy => policy.RequireRole("Admin"));
options.AddPolicy("UserPolicy", policy => policy.RequireRole("User"));
});
builder.Services.AddScoped<IRoleRepository, RoleRepository>();
builder.Services.AddScoped<ITokenServices, TokenServices>();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.MapOpenApi();
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.Run();
I am still learning how to handle Authentication, Authorization and Role Based Access Control as a whole within .NET so any guidance or advice is more than welcome!
Some pics displaying the error and the successful login.
https://postimg.cc/PP9Nxn9K https://postimg.cc/xkydtJCF https://postimg.cc/rRdMXKVT/4718a7f0