JWT Authentication

লেসন ৩০ / ৫০

JWT কী?

JSON Web Token — header.payload.signature, server stateless থাকে।

Setup

dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer

appsettings.json

{
  "Jwt": {
    "Key": "your-very-long-secret-min-32-characters",
    "Issuer": "MyApi",
    "Audience": "MyApiUsers",
    "ExpiryMinutes": 60
  }
}

Program.cs

using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using System.Text;

var jwt = builder.Configuration.GetSection("Jwt");
var key = Encoding.UTF8.GetBytes(jwt["Key"]!);

builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(opt => {
        opt.TokenValidationParameters = new TokenValidationParameters {
            ValidateIssuer = true,
            ValidIssuer = jwt["Issuer"],
            ValidateAudience = true,
            ValidAudience = jwt["Audience"],
            ValidateIssuerSigningKey = true,
            IssuerSigningKey = new SymmetricSecurityKey(key),
            ValidateLifetime = true,
            ClockSkew = TimeSpan.Zero
        };
    });

builder.Services.AddAuthorization();

// ...
app.UseAuthentication();
app.UseAuthorization();

Token Service

public interface ITokenService {
    string GenerateAccessToken(User user);
    string GenerateRefreshToken();
}

public class TokenService : ITokenService {
    private readonly JwtOptions _opt;
    public TokenService(IOptions<JwtOptions> opt) => _opt = opt.Value;

    public string GenerateAccessToken(User user) {
        var claims = new[] {
            new Claim(JwtRegisteredClaimNames.Sub, user.Id.ToString()),
            new Claim(JwtRegisteredClaimNames.Email, user.Email),
            new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
            new Claim(ClaimTypes.Role, user.Role)
        };

        var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_opt.Key));
        var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);

        var token = new JwtSecurityToken(
            issuer: _opt.Issuer,
            audience: _opt.Audience,
            claims: claims,
            expires: DateTime.UtcNow.AddMinutes(_opt.ExpiryMinutes),
            signingCredentials: creds);

        return new JwtSecurityTokenHandler().WriteToken(token);
    }

    public string GenerateRefreshToken() {
        var bytes = new byte[64];
        using var rng = RandomNumberGenerator.Create();
        rng.GetBytes(bytes);
        return Convert.ToBase64String(bytes);
    }
}

Auth Controller

[ApiController]
[Route("api/[controller]")]
public class AuthController : ControllerBase {
    private readonly AppDbContext _db;
    private readonly ITokenService _tokens;

    public AuthController(AppDbContext db, ITokenService tokens) {
        _db = db; _tokens = tokens;
    }

    [HttpPost("register")]
    public async Task<IActionResult> Register(RegisterDto dto) {
        if (await _db.Users.AnyAsync(u => u.Email == dto.Email))
            return Conflict("Email already registered");

        var user = new User {
            Email = dto.Email,
            Name = dto.Name,
            PasswordHash = BCrypt.Net.BCrypt.HashPassword(dto.Password),
            Role = "User"
        };
        _db.Users.Add(user);
        await _db.SaveChangesAsync();

        return Ok(new { token = _tokens.GenerateAccessToken(user) });
    }

    [HttpPost("login")]
    public async Task<IActionResult> Login(LoginDto dto) {
        var user = await _db.Users.FirstOrDefaultAsync(u => u.Email == dto.Email);
        if (user == null || !BCrypt.Net.BCrypt.Verify(dto.Password, user.PasswordHash))
            return Unauthorized("Invalid credentials");

        var refresh = _tokens.GenerateRefreshToken();
        user.RefreshToken = refresh;
        user.RefreshTokenExpiry = DateTime.UtcNow.AddDays(7);
        await _db.SaveChangesAsync();

        return Ok(new {
            accessToken = _tokens.GenerateAccessToken(user),
            refreshToken = refresh
        });
    }

    [HttpPost("refresh")]
    public async Task<IActionResult> Refresh(RefreshDto dto) {
        var user = await _db.Users.FirstOrDefaultAsync(u =>
            u.RefreshToken == dto.RefreshToken &&
            u.RefreshTokenExpiry > DateTime.UtcNow);

        if (user == null) return Unauthorized();

        return Ok(new { accessToken = _tokens.GenerateAccessToken(user) });
    }
}

Protected Endpoint

[ApiController]
[Route("api/[controller]")]
[Authorize]                         // ← সব action protected
public class ProductsController : ControllerBase {

    [HttpGet]
    [AllowAnonymous]                // ← public
    public IActionResult GetAll() => Ok();

    [HttpPost]
    [Authorize(Roles = "Admin")]    // ← role check
    public IActionResult Create() => Ok();

    [HttpGet("me")]
    public IActionResult Me() {
        var userId = User.FindFirst(JwtRegisteredClaimNames.Sub)?.Value;
        var email = User.FindFirst(JwtRegisteredClaimNames.Email)?.Value;
        return Ok(new { userId, email });
    }
}

Frontend Use

// Save token
localStorage.setItem("token", res.accessToken);

// Use
fetch("/api/products", {
    headers: { "Authorization": `Bearer ${token}` }
});