Đầ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.