Chapter 02

Authentication

Xác thực danh tính trong ASP.NET Core

Các phương pháp Authentication

ASP.NET Core hỗ trợ nhiều scheme xác thực khác nhau, mỗi loại phù hợp với một kiến trúc ứng dụng riêng:

🍪

Cookie Auth

Lưu phiên đăng nhập qua cookie trình duyệt

🔑

JWT Bearer

Token stateless cho API, không lưu trạng thái server

🔐

OAuth / OIDC

Đăng nhập qua bên thứ ba như Google, Facebook

👤

ASP.NET Identity

Framework quản lý user đầy đủ với database

Cách phổ biến nhất cho web apps. Server tạo cookie chứa thông tin phiên đăng nhập, browser tự động gửi cookie trong mỗi request.

Flow: Cookie Authentication

Browser
client
POST /login
credentials
Server Validate
kiểm tra
Set-Cookie
header
Browser Stores
lưu cookie
Auto-send Cookie
mỗi request
Server Reads
giải mã
ClaimsPrincipal
created

Cấu hình Cookie Authentication

C#
// Program.cs
builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
    .AddCookie(options =>
    {
        options.LoginPath = "/Account/Login";       // Redirect khi chưa đăng nhập
        options.AccessDeniedPath = "/Account/Denied"; // Redirect khi không có quyền
        options.ExpireTimeSpan = TimeSpan.FromHours(8); // Cookie hết hạn sau 8 giờ
        options.SlidingExpiration = true;             // Tự động gia hạn
    });

Login Action

C#
// AccountController.cs
[HttpPost("login")]
public async Task<IActionResult> Login(LoginModel model)
{
    var user = await _userService.ValidateAsync(model.Username, model.Password);
    if (user == null) return Unauthorized();

    var claims = new List<Claim>
    {
        new Claim(ClaimTypes.Name, user.Username),
        new Claim(ClaimTypes.Email, user.Email),
        new Claim(ClaimTypes.Role, user.Role)
    };

    var identity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
    var principal = new ClaimsPrincipal(identity);

    await HttpContext.SignInAsync(principal); // Tạo cookie và gửi cho browser
    return RedirectToAction("Index", "Home");
}
SlidingExpiration = true nghĩa là mỗi lần user truy cập, thời hạn cookie được reset. User chỉ bị logout nếu không hoạt động trong khoảng ExpireTimeSpan.

JWT Bearer Token

Phương pháp phổ biến cho API. Token tự chứa thông tin user, server không cần lưu session.

JWT Anatomy

Một JWT gồm 3 phần, ngăn cách bởi dấu chấm. Click vào từng phần để xem chi tiết:

HEADER
Algorithm & Type click to expand
PAYLOAD
Claims & Data click to expand
SIGNATURE
Verification click to expand
Header — chứa thuật toán mã hóa và loại token:
{
  "alg": "HS256",
  "typ": "JWT"
}

Header được encode bằng Base64Url, tạo thành phần đầu tiên của token.

Payload — chứa các claims (thông tin) về user:
{
  "sub": "1234567890",
  "email": "user@bugcreators.tech",
  "role": "Admin",
  "jti": "a1b2c3d4-e5f6-7890",
  "exp": 1700000000,
  "iss": "https://bugcreators.tech"
}

Lưu ý: Payload chỉ được encode, không được mã hóa. Ai cũng có thể đọc nội dung. Không bao giờ đặt mật khẩu hay thông tin nhạy cảm trong payload!

Signature — đảm bảo token không bị thay đổi:
HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  secret_key
)

Server dùng secret key để tạo signature. Khi nhận token, server tính lại signature và so sánh. Nếu khác nhau → token đã bị tamper.

Flow: JWT Authentication

Client
app / spa
POST /auth
credentials
Server
validate
Generate JWT
sign token
Client Stores
localStorage
GET /api
Bearer {token}
Validate Token
signature + claims
200 OK
response

Cấu hình JWT Authentication

C#
// Program.cs
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(options =>
    {
        options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuer = true,
            ValidateAudience = true,
            ValidateLifetime = true,
            ValidateIssuerSigningKey = true,
            ValidIssuer = "https://bugcreators.tech",
            ValidAudience = "https://bugcreators.tech",
            IssuerSigningKey = new SymmetricSecurityKey(
                Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"]!))
        };
    });

Tạo JWT Token

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

    var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config["Jwt:Key"]!));
    var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);

    var token = new JwtSecurityToken(
        issuer: "https://bugcreators.tech",
        audience: "https://bugcreators.tech",
        claims: claims,
        expires: DateTime.UtcNow.AddHours(2),
        signingCredentials: creds);

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

OAuth 2.0 & OpenID Connect

Cho phép user đăng nhập qua bên thứ ba (Google, Facebook, GitHub) mà không cần tự quản lý password.

OAuth 2.0 Authorization Code Flow

1
👆 User click "Đăng nhập với Google" trên ứng dụng của bạn
2
↗️ App redirect đến Google Authorization Server với client_id, redirect_uri, scope
3
User đăng nhập tại Google và đồng ý chia sẻ thông tin (email, profile)
4
↩️ Google redirect về app kèm Authorization Code qua query string
5
🔄 App gửi Authorization Code đến Google để đổi lấy Access Token (server-to-server)
6
📋 App dùng Access Token để lấy thông tin user từ Google UserInfo endpoint
7
🎉 App tạo ClaimsPrincipal từ thông tin nhận được và đăng nhập user (thường qua cookie)

Cấu hình Google OAuth

C#
builder.Services.AddAuthentication(options =>
{
    options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
    options.DefaultChallengeScheme = GoogleDefaults.AuthenticationScheme;
})
.AddCookie()
.AddGoogle(options =>
{
    options.ClientId = builder.Configuration["Google:ClientId"]!;
    options.ClientSecret = builder.Configuration["Google:ClientSecret"]!;
    options.Scope.Add("email");
    options.Scope.Add("profile");
});
OAuth 2.0 chỉ xử lý authorization (cho phép truy cập resource). OpenID Connect (OIDC) được xây dựng trên OAuth 2.0 và thêm lớp authentication (xác thực danh tính user). Khi dùng .AddGoogle(), ASP.NET Core tự động sử dụng OIDC.

ASP.NET Identity Framework

Framework đầy đủ cho quản lý user: đăng ký, đăng nhập, quên mật khẩu, xác thực email, 2FA... Được tích hợp sẵn với Entity Framework Core.

Kiến trúc phân lớp

Your Application Code
Controller, Razor Pages, Minimal APIs — nơi bạn viết business logic
UserManager<T>  |  SignInManager<T>  |  RoleManager<T>
Business Logic Layer — API cấp cao cho quản lý user, đăng nhập, role
IUserStore<T>  |  IRoleStore<T>
Data Access Abstraction — interface trừu tượng, có thể thay thế implementation
Entity Framework Core
Database Provider — SQL Server, PostgreSQL, SQLite...

Cấu hình Identity

C#
// Program.cs
builder.Services.AddIdentity<ApplicationUser, IdentityRole>(options =>
{
    options.Password.RequireDigit = true;
    options.Password.RequiredLength = 8;
    options.Lockout.MaxFailedAccessAttempts = 5;
    options.User.RequireUniqueEmail = true;
})
.AddEntityFrameworkStores<AppDbContext>()
.AddDefaultTokenProviders();

So sánh các phương pháp

Mỗi phương pháp có ưu và nhược điểm riêng. Lựa chọn phụ thuộc vào kiến trúc ứng dụng:

Phương pháp Phù hợp cho Ưu điểm Nhược điểm
Cookie Web app truyền thống (MVC, Razor Pages) Đơn giản, browser tự quản lý, hỗ trợ SlidingExpiration Không phù hợp cho API/mobile, dễ bị CSRF nếu không cấu hình đúng
JWT Bearer REST API, SPA, Mobile app, Microservices Stateless, dễ scale, cross-domain, chứa claims trong token Không thể revoke token ngay lập tức, kích thước lớn hơn cookie
OAuth / OIDC Đăng nhập qua bên thứ ba (social login) Không cần quản lý password, UX tốt, tin cậy từ provider lớn Phụ thuộc provider bên ngoài, cấu hình phức tạp hơn
Identity Web app cần quản lý user đầy đủ Đầy đủ tính năng (2FA, email confirm, lockout), tích hợp EF Core Nặng cho ứng dụng nhỏ, phụ thuộc database, learning curve cao