Giao diện
FAQ & Troubleshooting
Q: Làm sao thêm phương thức xác thực mới?
Dùng authManager.registerTypes() trong phương thức load() của plugin:
typescript
class MyOIDCAuth extends BaseAuth {
async validate() {
// Xác thực qua OIDC provider
const profile = await this.verifyOIDCToken(ctx);
return await this.authenticator.findOrCreateUser(profile.sub, {
displayname: profile.name,
email: profile.email,
});
}
}
// plugin.load()
this.app.authManager.registerTypes('oidc', {
auth: MyOIDCAuth,
title: 'OIDC Login',
getPublicOptions: (options) => ({
issuer: options.issuer,
clientId: options.clientId,
}),
});Sau khi đăng ký type, admin tạo authenticator mới với authType: 'oidc' qua UI hoặc API authenticators:create. Authenticator cần được enabled: true để hiển thị trên trang login.
Q: Token hết hạn, renew, blacklist hoạt động thế nào?
TokenController quản lý vòng đời JWT theo 3 mốc thời gian:
| Mốc | Cấu hình | Mặc định | Ý nghĩa |
|---|---|---|---|
| Token expiration | tokenExpirationTime | 1 ngày | Token hết hạn, nhưng có thể renew |
| Renew limit | expiredTokenRenewLimit | 1 ngày | Hết thời gian renew → phải đăng nhập lại |
| Session expiration | sessionExpirationTime | 7 ngày | Session hoàn toàn hết hạn (tính từ lúc signIn) |
Luồng:
- User đăng nhập →
TokenController.add()tạo recordissuedTokensvớijti,signInTime,issuedTime - Request bình thường → check
issuedTime + tokenExpirationTime > now→ OK - Token hết hạn → check
issuedTime + expiredTokenRenewLimit > now→ nếu còn, gọirenew(jti):- Tạo JTI mới bằng
randomUUID() - Update record trong
issuedTokens - Cache JTI mới 10 giây (xử lý concurrent request)
- Trả token mới qua header
x-new-token
- Tạo JTI mới bằng
- Hết renew limit hoặc session → reject, yêu cầu đăng nhập lại
TokenBlacklistService xử lý revoke:
has(token)— kiểm tra qua Bloom filter (nhanh) rồi DB (chính xác)add(token)— thêm vào Bloom filter + DB, đồng thời dọn token hết hạn- Bloom filter: capacity 1 triệu token, error rate 0.1%, chiếm khoảng 1.7 MB RAM
Q: Luồng quên mật khẩu hoạt động thế nào?
Yêu cầu cấu hình:
- Plugin
notification-managerphải được cài và có email channel hoạt động - Authenticator options cần có:
enableResetPassword: true,notificationChannel,emailSubject,emailContentType,emailContentHTMLhoặcemailContentText,resetTokenExpiresIn(phút)
Template variables có sẵn:
| Biến | Mô tả |
|---|---|
$user | Object user (id, username, email...) |
$resetLink | URL reset password đầy đủ |
$env | Environment variables (app.environment.getVariables()) |
$resetLinkExpiration | Thời gian hết hạn (phút) |
$systemSettings | Cài đặt hệ thống (title, logo...) |
Q: Sao user đăng nhập bị lỗi 401?
Kiểm tra theo thứ tự:
Authenticator có bật không?
- Gọi
GET /api/authenticators:publicListxem authenticator xuất hiện không - Kiểm tra trường
enabled: truetrong DB
- Gọi
Sai username/email hoặc password?
- Error message: "The username/email or password is incorrect"
BasicAuth.validate()tìm user bằng$or: [{ username }, { email }], sau đó verify password quaPasswordField.verify()
Token policy quá ngắn?
- Kiểm tra
tokenControlConfigtrong DB - Nếu
tokenExpirationTimequá nhỏ, token hết hạn ngay lập tức - Nếu
sessionExpirationTimequá nhỏ, session hết hạn nhanh
- Kiểm tra
Token bị blacklist?
- Kiểm tra bảng
tokenBlacklistcó chứa token không - Nếu dùng Redis Bloom filter, kiểm tra service hoạt động bình thường
- Kiểm tra bảng
Header X-Authenticator sai?
- Client phải gửi header
X-Authenticatorđúng tên authenticator (ví dụ:basic) - Nếu không gửi, server dùng default authenticator (authenticator đầu tiên trong cache)
- Client phải gửi header
Q: Có thể dùng nhiều authenticators cùng lúc không?
Có. Hệ thống hỗ trợ nhiều authenticator hoạt động đồng thời:
authenticators:publicListtrả về tất cả authenticator cóenabled: true, sắp xếp theosort- Client hiển thị danh sách trên trang login, mỗi authenticator có UI riêng
- Khi đăng nhập, client gửi header
X-Authenticator: <name>để chỉ định authenticator
Ví dụ cấu hình phổ biến:
basic(Email/Password) — luôn bậtoidc-google(Google Login) — bật thêmldap-corp(LDAP nội bộ) — bật cho nhân viên
Lưu ý: Không thể tắt hoặc xóa authenticator cuối cùng. Server kiểm tra ít nhất 1 authenticator bật (checkCount trong authenticators:destroy và authenticators:update).
Q: Environment variables trong authenticator options dùng thế nào?
Storer.renderJsonTemplate() tự động thay thế biến môi trường trong trường options trước khi cache authenticator.
Ví dụ cấu hình OIDC trong DB:
json
{
"clientId": "$env.OIDC_CLIENT_ID",
"clientSecret": "$env.OIDC_CLIENT_SECRET",
"issuer": "$env.OIDC_ISSUER"
}Khi Storer load authenticator, app.environment.renderJsonTemplate() thay thế $env.* bằng giá trị thực từ environment.
Ngoại lệ: Một số key không được render template để tránh injection. BasicAuth khai báo:
typescript
static readonly optionsKeysNotAllowedInEnv = [
'emailContentText',
'emailContentHTML',
'emailSubject'
];Các key này được truyền qua tham số omit khi gọi renderJsonTemplate, giữ nguyên nội dung gốc.
Q: Token blacklist dùng Bloom filter có bị false positive không?
Bloom filter được cấu hình với error rate 0.1% (1/1000) và capacity 1 triệu token. Điều này có nghĩa:
- False positive (khoảng 0.1%): Bloom filter báo token bị block nhưng thực tế không — không ảnh hưởng vì
has()luôn kiểm tra DB sau Bloom filter - False negative (0%): Nếu token thực sự bị block, Bloom filter luôn phát hiện
Nếu Redis không khả dụng, service fallback về chỉ dùng DB query. Bloom filter khởi tạo lúc beforeStart, nạp tất cả token chưa hết hạn từ DB.