Cấp bậc tác giả:

DOTNET

Xây dựng hệ thống xác thực bảo mật với JWT và Refresh Token trong ASP.NET Core

Được viết bởi webmaster ngày 06/07/2025 lúc 12:03 PM
Trong bài học này, sinh viên sẽ được hướng dẫn cách triển khai hệ thống xác thực người dùng bằng JWT (JSON Web Token) trong ASP.NET Core, kết hợp với cơ chế Refresh Token để tăng cường bảo mật và trải nghiệm người dùng. Ngoài ra, bài học còn hướng dẫn cách thu hồi (revoke) token và tái cấp quyền truy cập an toàn.
  • 0
  • 38

Xây dựng hệ thống xác thực bảo mật với JWT và Refresh Token trong ASP.NET Core

Đầy đủ chức năng:

  • Đăng nhập bằng JWT (Access Token + Refresh Token)

  • Tự động gọi API khi đã đăng nhập

  • Tự động làm mới (refresh) access token khi hết hạn

  • Logout (thu hồi refresh token)

Cấu trúc Project
MyAuthApp/
├── MyAuthApp.Api/        # ASP.NET Core Web API (Controller cấp token)
├── MyAuthApp.Web/        # Razor Pages (giao diện người dùng, gọi API)

1. MyAuthApp.Api – Cấp và xác thực Token

Startup cấu hình JwtBearer và refresh token.

TokenController.cs
[ApiController]
[Route("api/[controller]")]
public class TokenController : ControllerBase
{
    private readonly ITokenService _tokenService;

    public TokenController(ITokenService tokenService)
    {
        _tokenService = tokenService;
    }

    [HttpPost("login")]
    public IActionResult Login([FromBody] LoginRequest request)
    {
        if (request.Username == "admin" && request.Password == "123456")
        {
            var tokens = _tokenService.GenerateTokens(request.Username);
            return Ok(tokens);
        }

        return Unauthorized();
    }

    [HttpPost("refresh")]
    public IActionResult Refresh([FromBody] RefreshRequest request)
    {
        var result = _tokenService.RefreshToken(request.RefreshToken);
        return result is null ? Unauthorized() : Ok(result);
    }

    [HttpPost("revoke")]
    public IActionResult Revoke([FromBody] string refreshToken)
    {
        _tokenService.Revoke(refreshToken);
        return Ok();
    }
}
TokenService.cs
public class TokenService : ITokenService
{
    private readonly IConfiguration _config;
    private readonly List<string> _validRefreshTokens = new(); // lưu bộ nhớ

    public TokenService(IConfiguration config) => _config = config;

    public TokenResponse GenerateTokens(string username)
    {
        var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config["Jwt:Key"]!));
        var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
        var token = new JwtSecurityToken(
            issuer: _config["Jwt:Issuer"],
            audience: _config["Jwt:Audience"],
            claims: new[] { new Claim(ClaimTypes.Name, username) },
            expires: DateTime.UtcNow.AddSeconds(30),
            signingCredentials: creds
        );

        var accessToken = new JwtSecurityTokenHandler().WriteToken(token);
        var refreshToken = Guid.NewGuid().ToString();

        _validRefreshTokens.Add(refreshToken);
        return new TokenResponse { AccessToken = accessToken, RefreshToken = refreshToken };
    }

    public TokenResponse? RefreshToken(string refreshToken)
    {
        if (!_validRefreshTokens.Contains(refreshToken))
            return null;

        _validRefreshTokens.Remove(refreshToken);
        return GenerateTokens("admin");
    }

    public void Revoke(string refreshToken)
    {
        _validRefreshTokens.Remove(refreshToken);
    }
}
Models
public record LoginRequest(string Username, string Password);
public record RefreshRequest(string RefreshToken);
public record TokenResponse
{
    public string AccessToken { get; set; } = "";
    public string RefreshToken { get; set; } = "";
}

2. MyAuthApp.Web – Razor Pages frontend

Login.cshtml.cs

public class LoginModel : PageModel
{
    private readonly IHttpClientFactory _clientFactory;
    public LoginModel(IHttpClientFactory clientFactory) => _clientFactory = clientFactory;

    [BindProperty]
    public string Username { get; set; }
    [BindProperty]
    public string Password { get; set; }

    public async Task<IActionResult> OnPostAsync()
    {
        var client = _clientFactory.CreateClient();
        var response = await client.PostAsJsonAsync("https://localhost:5001/api/token/login", new
        {
            Username,
            Password
        });

        if (response.IsSuccessStatusCode)
        {
            var tokens = await response.Content.ReadFromJsonAsync<TokenResponse>();
            HttpContext.Session.SetString("access_token", tokens.AccessToken);
            HttpContext.Session.SetString("refresh_token", tokens.RefreshToken);
            return RedirectToPage("/Index");
        }

        ModelState.AddModelError("", "Invalid login");
        return Page();
    }
}
Index.cshtml.cs – Gọi API kèm tự động refresh nếu access token hết hạn

public class IndexModel : PageModel
{
    private readonly IHttpClientFactory _clientFactory;
    private readonly ILogger<IndexModel> _logger;

    public string ApiResponse { get; set; }

    public IndexModel(IHttpClientFactory clientFactory, ILogger<IndexModel> logger)
    {
        _clientFactory = clientFactory;
        _logger = logger;
    }

    public async Task<IActionResult> OnGetAsync()
    {
        var accessToken = HttpContext.Session.GetString("access_token");
        var refreshToken = HttpContext.Session.GetString("refresh_token");

        var client = _clientFactory.CreateClient();
        client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);

        var response = await client.GetAsync("https://localhost:5001/api/secure-data");

        if (response.StatusCode == HttpStatusCode.Unauthorized && !string.IsNullOrEmpty(refreshToken))
        {
            // Thử refresh token
            var refreshResponse = await client.PostAsJsonAsync("https://localhost:5001/api/token/refresh",
                new { RefreshToken = refreshToken });

            if (refreshResponse.IsSuccessStatusCode)
            {
                var tokens = await refreshResponse.Content.ReadFromJsonAsync<TokenResponse>();
                HttpContext.Session.SetString("access_token", tokens.AccessToken);
                HttpContext.Session.SetString("refresh_token", tokens.RefreshToken);

                client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", tokens.AccessToken);
                response = await client.GetAsync("https://localhost:5001/api/secure-data");
            }
            else
            {
                return RedirectToPage("/Login");
            }
        }

        ApiResponse = await response.Content.ReadAsStringAsync();
        return Page();
    }
}

3. Startup cấu hình Razor Pages sử dụng Session

Program.cs
builder.Services.AddRazorPages();
builder.Services.AddSession();
builder.Services.AddHttpClient();
builder.Services.AddSingleton<ITokenService, TokenService>();

var app = builder.Build();
app.UseSession();
app.MapRazorPages();
app.Run();

Logout.cshtml.cs – Gọi API để revoke và xóa token khỏi session
public class LogoutModel : PageModel
{
    private readonly IHttpClientFactory _clientFactory;
    private readonly ILogger<LogoutModel> _logger;

    public LogoutModel(IHttpClientFactory clientFactory, ILogger<LogoutModel> logger)
    {
        _clientFactory = clientFactory;
        _logger = logger;
    }

    public async Task<IActionResult> OnGetAsync()
    {
        var refreshToken = HttpContext.Session.GetString("refresh_token");

        if (!string.IsNullOrEmpty(refreshToken))
        {
            var client = _clientFactory.CreateClient();
            await client.PostAsJsonAsync("https://localhost:5001/api/token/revoke", refreshToken);
        }

        // Xóa toàn bộ session
        HttpContext.Session.Clear();

        return RedirectToPage("/Login");
    }
}
Logout.cshtml
@page
@model LogoutModel
@{
    Layout = null;
}
<p>Đang đăng xuất...</p>
SecureController.cs – API yêu cầu JWT 
[Authorize]
[ApiController]
[Route("api/[controller]")]
public class SecureController : ControllerBase
{
    [HttpGet("data")]
    public IActionResult GetSecureData()
    {
        var username = User.Identity?.Name ?? "unknown";
        return Ok($"Dữ liệu bảo vệ chỉ dành cho: {username}");
    }
}
Endpoint này cần token hợp lệ, được xác thực bằng [Authorize]. 
Khi token hết hạn hoặc không có token → trả về 401 Unauthorized.




Nguồn bài viết: Medium

BÌNH LUẬN BÀI VIẾT

Bài viết mới nhất

LIKE BOX

Bài viết được xem nhiều nhất

HỌC HTML