Chapter 4: Authentication and Authorization
4.1 Introduction to Authentication and Authorization
Authentication and authorization are essential components of building secure APIs. Authentication verifies the identity of a user or client, while authorization determines their access rights to specific resources or actions within the API.
4.2 JWT Authentication in ASP.NET Core
JWT (JSON Web Token) authentication is a widely used mechanism for securing APIs. It involves the use of digitally signed tokens to authenticate and authorize users. In this chapter, we will explore a practical code example of implementing JWT authentication with roles, expiry, and cookies in an ASP.NET Core API.
4.2.1 Installing Required Packages
Before we begin, ensure that you have the necessary NuGet packages installed. Open the NuGet Package Manager Console and execute the following command:
dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer
4.2.2 Configuring JWT Authentication
Configure JWT authentication in the Startup.cs
file. Open the file and modify the ConfigureServices
method as follows:
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateIssuerSigningKey = true,
ValidIssuer = "your-issuer",
ValidAudience = "your-audience",
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("your-secret-key"))
};
});
Make sure to replace "your-issuer"
, "your-audience"
, and "your-secret-key"
with your own values. This configuration sets up JWT authentication and defines the validation parameters for the JWT token.
4.2.3 Generating a JWT Token with Roles and Expiry
To generate a JWT token with roles and an expiry date, you can modify the GenerateJwtToken
method as follows:
private string GenerateJwtToken(string username, List<string> roles)
{
var claims = new List<Claim>
{
new Claim(ClaimTypes.Name, username),
new Claim(ClaimTypes.Role, string.Join(",", roles)),
// Add additional claims as needed
};
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("your-secret-key"));
var credentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
var token = new JwtSecurityToken(
issuer: "your-issuer",
audience: "your-audience",
claims: claims,
expires: DateTime.UtcNow.AddHours(1),
signingCredentials: credentials
);
return new JwtSecurityTokenHandler().WriteToken(token);
}
In this example, the GenerateJwtToken
method accepts a list of roles as a parameter. The roles are added as claims using the ClaimTypes.Role
claim type. Additionally, the token is set to expire after one hour (you can adjust the expiry time as per your requirements).
4.2.4 Creating a JWT Token and Storing it in a Cookie
To generate a JWT token and store it in a cookie upon successful authentication, you can modify the authentication logic as follows:
[HttpPost("login")]
public IActionResult Login(LoginViewModel model)
{
// Authenticate the user
var user = AuthenticateUser(model.Username, model.Password);
if (user != null)
{
// Generate JWT token
var token = GenerateJwtToken(user.Username, user.Roles);
// Create a cookie and store the token
var cookie
Options = new CookieOptions
{
HttpOnly = true,
Secure = true,
SameSite = SameSiteMode.Strict,
Expires = DateTime.UtcNow.AddHours(1) // Set the expiry time of the cookie
};
HttpContext.Response.Cookies.Append("jwtToken", token, cookieOptions);
return Ok(new { token });
}
return Unauthorized();
}
In this example, after authenticating the user, the GenerateJwtToken
method is called to generate a JWT token. The token is then stored in a cookie named “jwtToken” using the HttpContext.Response.Cookies.Append
method. The cookie is set to be HTTP-only, secure, and have a strict same-site policy. The expiry time of the cookie is also set to one hour, matching the token expiry.
In this example, the AuthenticateUser
method accepts the username and password as parameters. It retrieves user information from a data source, such as a database. In this case, we are using a mock implementation with a list of User
objects.
private User AuthenticateUser(string username, string password)
{
// Retrieve user information from the database or any other data source
// Here, we will use a mock implementation for demonstration purposes
var users = new List<User>
{
new User { Id = 1, Username = "john", Password = "password123", Roles = new List<string> { "admin" } },
new User { Id = 2, Username = "jane", Password = "password456", Roles = new List<string> { "user" } }
};
// Find the user with the matching username and password
var user = users.FirstOrDefault(u => u.Username == username && u.Password == password);
return user;
}
The method then searches for a user with the matching username and password. If a match is found, the corresponding User
object is returned. Otherwise, null
is returned, indicating that the authentication failed.
4.2.5 Protecting Endpoints with Role-Based Authorization and JWT Authentication
To protect your API endpoints based on user roles, you can use the [Authorize(Roles = "role")]
attribute. Here’s an example of how to secure an endpoint with role-based authorization:
[Authorize(Roles = "admin")]
[HttpGet("adminOnly")]
public IActionResult AdminOnlyEndpoint()
{
// Endpoint logic for admin users
return Ok("This is an admin-only endpoint.");
}
[Authorize(Roles = "admin,user")]
[HttpGet("adminOrUser")]
public IActionResult AdminOrUserEndpoint()
{
// Endpoint logic for admin and user roles
return Ok("This endpoint is accessible to both admin and user roles.");
}
In this example, the AdminOnlyEndpoint
endpoint is accessible only to users with the “admin” role, while the AdminOrUserEndpoint
endpoint is accessible to users with either the “admin” or “user” role. The [Authorize(Roles = "role")]
attribute ensures that only users with the specified roles can access the endpoints.
4.3 Real-Life Practical Use of JWT Authentication with Roles and Cookies
In a real-life scenario, after successful authentication, the API generates a JWT token containing the user’s roles. The token is then stored in a cookie. Clients can include this token in the Authorization
header of subsequent requests, or the token will be automatically sent with each request via the cookie. This allows the API to authenticate the user and authorize access to specific endpoints based on their assigned roles.
By implementing JWT authentication with roles, expiry, and cookies in your ASP.NET Core API, you can enhance security, control access based on user roles, and provide a seamless authentication experience for your users.
In the next chapter, we will explore error handling and logging strategies in ASP.NET Core API, providing robust error handling mechanisms and logging capabilities.