.NET 6 - Authentication and Authorization with JWT
文章目錄
In this article, we will use .NET 6 to authenticate and authorize user with JWT in role based.
Design
- Authenticate user and generate JWT.
- Pass JWT in each request.
- Validate JWT.
- Authorize
Implementaion
1. Models
- User
public class User { public int UserId { get; set; } public string UserName { get; set; } public string Password { get; set; } public UserRole Role { get; set; } public string? Token { get; set; } }
- UserRole
public enum UserRole { Admin, User }
2. Fakedata
- Package required
- BCrypt.Net-Next
- BCrypt.Net-Next
- Encrypt user’s password by
HashPassword
method in BCrypt.
// UserService.cs
private List<User> _users = new List<User>
{
new User { UserId = 1, UserName = "admin", Password = BCrypt.Net.BCrypt.HashPassword("admin"), Role = UserRole.Admin },
new User { UserId = 2, UserName = "test", Password = BCrypt.Net.BCrypt.HashPassword("test"), Role = UserRole.User }
};
3. Authenticate
- Verify username and password
- Use
Verify
method in Bcrypt to verify encrypted password. - If verfify success, then generate JWT token.
- Use
// UserService.cs
public User Authenticate(User model)
{
User user = _users.SingleOrDefault(m => m.UserName == model.UserName);
if(user == null || !BCrypt.Net.BCrypt.Verify(model.Password, user.Password))
throw new Exception("Username or password is incorrect");
user.Token = _jwtUtils.GenerateJwtToken(user);
return user;
}
- Generate JWT token
- Package required
- Microsoft.IdentityModel.Tokens
- System.IdentityModel.Tokens
- Package required
// JwtUtil.cs
public string GenerateJwtToken(User user)
{
// generate token that is valid for 7 days
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes(_config["JWT:SignKey"]);
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new[] { new Claim("id", user.UserId.ToString()) }),
Expires = DateTime.UtcNow.AddDays(7),
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
};
var token = tokenHandler.CreateToken(tokenDescriptor);
return tokenHandler.WriteToken(token);
}
4. Request with JWT token in Header
- Test with swagger, config security in Swagger generator.
// Program.cs
builder.Services.AddSwaggerGen(options => {
options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
{
Name = "Authorization",
Type = SecuritySchemeType.ApiKey,
Scheme = "Bearer",
BearerFormat = "JWT",
In = ParameterLocation.Header,
Description = "JWT Authorization header using the Bearer scheme."
});
options.AddSecurityRequirement(new OpenApiSecurityRequirement
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Type = ReferenceType.SecurityScheme,
Id = "Bearer"
}
},
new string[] {}
}
});
});
5. Validate JWT token
- Add Middleware for each of the requests to check whether token is valid before executing controller.
// Program.cs
app.UseMiddleware<JwtMiddleware>();
- If token is valid, memorize the user in HttpContext for authorization.
// JwtMiddleware.cs
private readonly RequestDelegate _next;
public JwtMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext context, UserService userService, JwtUtil jwtUtil)
{
var token = context.Request.Headers["Authorization"].FirstOrDefault();
var userId = jwtUtil.ValidateJwtToken(token);
if (userId != null)
{
// attach user to context on successful jwt validation
context.Items["User"] = userService.GetUser(userId.Value);
}
await _next(context);
}
- Verify token by
ValidateToken
method in JwtSecurityTokenHandler
// JwtUtil.cs
public int? ValidateJwtToken(string token)
{
if (token == null)
return null;
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes(_config["JWT:SignKey"]);
try
{
tokenHandler.ValidateToken(token, new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(key),
ValidateIssuer = false,
ValidateAudience = false,
// set clockskew to zero so tokens expire exactly at token expiration time (instead of 5 minutes later)
ClockSkew = TimeSpan.Zero
}, out SecurityToken validatedToken);
var jwtToken = (JwtSecurityToken)validatedToken;
var userId = int.Parse(jwtToken.Claims.First(x => x.Type == "id").Value);
// return user id from JWT token if validation successful
return userId;
}
catch
{
// return null if validation fails
return null;
}
}
6. Authorization
- Customize Attribute
- AllowAnonymousAttribute
[AttributeUsage(AttributeTargets.Method)] public class AllowAnonymousAttribute : Attribute {}
- AuthorizeAttribute
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] public class AuthorizeAttribute : Attribute, IAuthorizationFilter { private readonly IList<UserRole> _roles; public AuthorizeAttribute(params UserRole[] roles) { _roles = roles ?? new UserRole[] { }; } public void OnAuthorization(AuthorizationFilterContext context) { // skip authorization if action is decorated with [AllowAnonymous] attribute var allowAnonymous = context.ActionDescriptor.EndpointMetadata.OfType<AllowAnonymousAttribute>().Any(); if (allowAnonymous) return; // authorization var user = (User)context.HttpContext.Items["User"]; if (user == null || (_roles.Any() && !_roles.Contains(user.Role))) { // not logged in or role not authorized context.Result = new JsonResult(new { message = "Unauthorized" }) { StatusCode = StatusCodes.Status401Unauthorized }; } } }
7. Controller
- Before execute the method, use attibute properties that we customized, such as
AllowAnonymous
andAuthorize
to check whether the user can access or not.
// UserController.cs
[AllowAnonymous]
[HttpPost]
public User Login(User user)
{
return _userService.Authenticate(user);
}
[Authorize(UserRole.Admin)]
[HttpGet]
public List<User> GetAllUsers()
{
return _userService.GetAllUsers();
}
[HttpGet("{id}")]
public User GetUser(int id)
{
return _userService.GetUser(id);
}
Reference
- .NET 6.0 - Role Based Authorization Tutorial with Example API
- 在 Swagger UI 加上驗證按鈕,讓 Request Header 傳遞 Authorize Token