JWT (JSON Web Token) is a popular way to implement secure authentication in modern web applications. It provides a lightweight and stateless mechanism to authenticate users, ensuring secure data transfer. In this blog post, we’ll explore how to implement JWT authentication in a C# ASP.NET Core application with a step-by-step example.
What is JWT?
A JWT is a token that is used to securely transmit information between two parties (client and server). It is digitally signed, ensuring that the data it contains can be trusted. JWT consists of three parts:
- Header: Specifies the algorithm used to generate the signature, typically HMAC SHA256 or RSA.
- Payload: Contains the claims or the data being transmitted (such as user ID, roles, etc.).
- Signature: Ensures that the token hasn’t been tampered with.
The general structure looks like this:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6InVzZXIwMSIsIm5iZiI6MTczMTIxMDQ4OCwiZXhwIjoxNzMxMjExNjg4LCJpYXQiOjE3MzEyMTA0ODh9.2adOgvFCgF4FfzwWS3VbT-AOUvXvwwMmI76HrdTXFW4
Why JWT?
- Stateless: No need to store sessions on the server.
- Scalable: The server doesn’t need to store or retrieve session information.
- Cross-domain: JWT can be easily used across different domains, making it ideal for distributed applications like microservices.
Implementing JWT in C# ASP.NET Core
Lets' build a simple ASP.NET Core Web Api and implement JWT Authentication. In this demo project we will have three projects-
- JWTAuthentication.WebApi: This will be Api in which we will have two endpoints, one to Authenticate the user and return JWT Token. Another endpoint will be secure which will return data only for valid tokens.
- JwtAuthentication.Services:This will be our servcie layer.
- JWTAuthentication.TokenManager:This will be class library where we will implement JWT token generation.
Lets start the implementation-
Setting Up the Project
- Open Visual Studio and create a new ASP.NET Core project JWTAuthentication.WebApi.
- Add a class library in this project name JwtAuthentication.Services
- Add another class library in this project name JwtAuthentication.TokenManager
JWTAuthentication.TokenManager Project
- Install the required NuGet packages in JWTAuthentication.TokenManager Project
dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer
- Add a class file JwtExtension.cs and Create a extension method for Registering and configure the JWT Authentication.
public static IServiceCollection AddCustomJwtAuthentication(this IServiceCollection services) { services.AddAuthentication(option => { option.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; option.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; option.DefaultScheme = JwtBearerDefaults.AuthenticationScheme; }).AddJwtBearer(x => { x.RequireHttpsMetadata = false; x.SaveToken = false; x.TokenValidationParameters = new TokenValidationParameters { ValidateIssuerSigningKey = true, IssuerSigningKey = new SymmetricSecurityKey( Encoding.UTF8 .GetBytes(JWTTokenManager.JWT_Secret) ), ValidateIssuer = false, ValidateAudience = false, ClockSkew = TimeSpan.Zero }; }); return services; }
- Add a class file JWTTokenManager.cs and add the below code.
public class JWTTokenManager { public static string JWT_Secret = "Your Secret Key"; private static int JWT_TOKEN_VALIADITY_MIN = 20; public string GenerateJwtToken(string userName) { var claims = new List<Claim> { new Claim(ClaimTypes.Name, userName), }; var key = Encoding.ASCII.GetBytes(JWT_Secret); var tokenHandler = new JwtSecurityTokenHandler(); var tokenDescriptor = new SecurityTokenDescriptor { Subject = new ClaimsIdentity(claims), Expires = DateTime.UtcNow.AddMinutes(JWT_TOKEN_VALIADITY_MIN), SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature) }; var token = tokenHandler.CreateToken(tokenDescriptor); return tokenHandler.WriteToken(token); } }
In the above code , you have created a method to generate the JWT Token when a user successfully logs in.
JwtAuthentication.Services Project
- Add JWTAuthentication.TokenManager as reference in JwtAuthentication.Services Project. In this project we will verify the user based on user name and password and on successfull verofication, we will call JWT token creation method which we have implemented in JWTAuthentication.TokenManager project
- Models-Add below class files in this project-AuthenticateRequest.cs, AuthenticateResponse.cs,UserAccount.cs
//AuthenticateRequest.cs public class AuthenticateRequest { [Required] public string Username { get; set; } [Required] public string Password { get; set; } } //AuthenticateResponse.cs public class AuthenticateResponse { public string Username { get; set; } public string Token { get; set; } } //UserAccount public class UserAccount { [JsonIgnore] public string UserName { get; set; } public string FirstName { get; set; } public string LastName { get; set; } [JsonIgnore] public string Password { get; set; } }
- Add file UserServiceExtensions.cs. In this file we will create a extension method of IServiceCollection and add the dependencies-
public static IServiceCollection AddUserServiceDepedencies(this IServiceCollection services) { services.AddSingleton^lt;JWTTokenManager>(); services.AddSingleton<IUserService, UserService>(); return services; }
- Add file UserService.cs and copy pase the below code-
public interface IUserService { AuthenticateResponse? Authenticate(AuthenticateRequest model); IEnumerable<UserAccount> GetAll(); } public class UserService : IUserService { // users hardcoded for simplicity, store in a db with hashed passwords in production applications private List<UserAccount> _users = new List<UserAccount> { new UserAccount { FirstName = "Test", LastName = "User", UserName = "user01", Password = "user01" }, new UserAccount { FirstName = "Test1", LastName = "User", UserName = "user02", Password = "user02" } }; private readonly JWTTokenManager _jwtTokenManager; public UserService(JWTTokenManager jwtTokenManager) { _jwtTokenManager = jwtTokenManager; } public AuthenticateResponse? Authenticate(AuthenticateRequest model) { var user = _users.SingleOrDefault(x => x.UserName == model.Username && x.Password == model.Password); // return null if user not found if (user == null) return null; // authentication successful so generate jwt token var token = _jwtTokenManager.GenerateJwtToken(user.UserName); return new AuthenticateResponse { Token = token, Username = user.UserName }; } public IEnumerable<UserAccount> GetAll() { return _users; } }
Here, we have Authenticate method to validate the user by username and password.On successfull validation, we are calling GenerateJwtToken method to generate the new JWT Token
JWTAuthentication.WebApi
- Add JWTAuthentication.TokenManager and JwtAuthentication.Services as reference.
- Modify the Program.cs file to configure the JWT authentication. Add the following to enable JWT authentication
builder.Services.AddCustomJwtAuthentication(); builder.Services.AddAuthorization(); builder.Services.AddUserServiceDepedencies(); builder.Services.AddControllers();
- Add authentication middleware-
// Enable Authentication and Authorization app.UseAuthentication(); app.UseAuthorization();
Program.cs file will look like this-
using JWTAuthentication.TokenManager.Extensions; using JwtAuthentication.Services.Extensions; var builder = WebApplication.CreateBuilder(args); builder.Services.AddCustomJwtAuthentication(); builder.Services.AddAuthorization(); builder.Services.AddUserServiceDepedencies(); builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); builder.Services.AddControllers(); var app = builder.Build(); // Configure the HTTP request pipeline. if (app.Environment.IsDevelopment()) { app.UseSwagger(); app.UseSwaggerUI(); } app.UseHttpsRedirection(); app.UseAuthentication(); app.UseAuthorization(); app.MapControllers(); app.Run();
- Creating a Authenticate Endpoint :In your controller, create an action that logs in the user and returns a token.
[HttpPost("authenticate")] public IActionResult Authenticate(AuthenticateRequest model) { var response = _userService.Authenticate(model); if (response == null) return BadRequest(new { message = "Username or password is incorrect" }); return Ok(response); }
- Securing API Endpoints :Now that we have JWT authentication set up, let’s secure an API endpoint. Add the [Authorize] attribute to a controller action that you want to protect
[Authorize] [HttpGet] public IActionResult GetAll() { var users = _userService.GetAll(); return Ok(users); }
This ensures that the GetSecureData method can only be accessed by users who have a valid JWT token.
Testing the API
You can test your API using a tool like Postman or cURL.
- First, make a POST request to your authenticate endpoint in this case /api/User/authenticate with the following JSON in the body:
{ "username": "user01", "password": "user01" }
You should receive a JWT token in the response.
- Use this token in the Authorization header for subsequent requests to the secure endpoint.
Authorization: Bearer <your-token-here>
Making a GET request to /api/User with the token should return the protected data.
The full source code is available here:
Conclusion
JWT authentication is a robust and stateless method for securing your ASP.NET Core applications. By following this guide, you now have a basic implementation of JWT authentication in a C# Web API. This approach not only improves security but also simplifies scalability by reducing the need for server-side session management.
Happy coding!! 😊
No comments:
Post a Comment