Hợp đồng claim của token — GIW POC
Trạng thái: POC · Ngày: 21/09/2026 Các claim dưới đây sao chép từ một token thật do stack đang chạy phát ra, không viết theo trí nhớ.
1. Quy tắc đứng trên mọi quy tắc khác
citizenId/ CCCD tuyệt đối không được xuất hiện trong bất kỳ token nào, dưới bất kỳ dạng nào, với bất kỳ tên claim nào.
Điều này được một test tự động kiểm (tests/e2e.mjs → "token contains NO citizenId in any claim"); test sẽ đánh trượt bản build nếu có giá trị gốc hoặc bất kỳ tên nào trong citizenId, cccd, CCCD, citizen_id, nationalId.
2. Phân loại claim
| Lớp | Nghĩa là gì |
|---|---|
standard |
Do OIDC Core / RFC 9068 định nghĩa. Không định nghĩa lại. |
keycloak |
Riêng của Keycloak nhưng là chuẩn của sản phẩm này. |
custom |
Do Galaxy định nghĩa, phát ra bởi client scope giw-identity. |
poc-only |
Tồn tại vì đây là POC; không phải cam kết cho production. |
production-candidate |
Claim tự định nghĩa mà chúng ta dự kiến giữ lại. |
3. Token CUSTOMER
Phát qua confidential client wifi-portal-api, bằng password grant hoặc đăng ký rồi đăng nhập. Không có redirect trình duyệt nào.
| Claim | Lớp | Ví dụ | Ghi chú |
|---|---|---|---|
iss |
standard | http://localhost:8080/realms/galaxy-id-poc |
Entitlement có verify |
sub |
standard | 9e115334-e225-4a70-a4b7-e785030ce188 |
Subject id ổn định |
aud |
standard | ["account"] |
Xem §6 — hôm nay azp mới là tín hiệu client thật |
azp |
standard | wifi-portal-api |
|
exp / iat / auth_time |
standard | vòng đời 900 s | |
jti |
standard | ||
typ |
keycloak | Bearer |
|
sid |
keycloak | Id của phiên SSO | |
acr |
keycloak | 1 |
|
scope |
standard | openid email giw-identity profile |
|
realm_access.roles |
keycloak | default-roles-galaxy-id-poc, … |
Policy của GIW không dùng |
resource_access |
keycloak | Policy của GIW không dùng | |
allowed-origins |
keycloak | ["http://localhost:3000"] |
|
preferred_username |
standard | an.nguyen@example.test |
|
email / email_verified |
standard | ||
name / given_name / family_name |
standard | ||
user_type |
custom · production-candidate | CUSTOMER |
Lấy từ user attribute. Vắng mặt với người tự đăng ký cho tới khi được đặt — Entitlement coi vắng mặt là CUSTOMER. |
4. Token EMPLOYEE
Phát qua confidential client wifi-portal-employee-api, sau khi direct-grant authenticator tuỳ biến thành công bên trong Keycloak.
| Claim | Lớp | Ví dụ | Ghi chú |
|---|---|---|---|
| (toàn bộ claim CUSTOMER ở trên) | |||
azp |
standard | wifi-portal-employee-api |
Phân biệt luồng |
aud |
standard | ["wifi-portal-employee-api","account"] |
Audience mapper thêm client vào |
user_type |
custom · production-candidate | EMPLOYEE |
|
employee_verified |
custom · production-candidate | true (boolean, không phải chuỗi) |
Chỉ được đặt bởi authenticator sau khi MATCH + ACTIVE |
employee_ref |
custom · production-candidate | EMP-12345 |
Mã tham chiếu mờ do HR cấp |
company |
custom · production-candidate | VIETJET |
Quyết định điều kiện hưởng entitlement |
preferred_username |
keycloak | emp-12345 |
Suy ra từ employee_ref, tất định |
name |
standard | Employee EMP-12345 |
Chỗ điền tạm. HR cố ý không trả về tên thật. |
Token nhân viên thật, nguyên văn
{
"iss": "http://localhost:8080/realms/galaxy-id-poc",
"sub": "8f189cbd-70a4-466d-a11f-dae10d89874c",
"aud": ["wifi-portal-employee-api", "account"],
"azp": "wifi-portal-employee-api",
"typ": "Bearer",
"exp": 1789966050,
"iat": 1789965150,
"auth_time": 1789965150,
"scope": "openid email giw-identity profile",
"user_type": "EMPLOYEE",
"employee_verified": true,
"employee_ref": "EMP-12345",
"company": "VIETJET",
"preferred_username": "emp-12345"
}
Để ý thứ không có: không citizenId, không tên nhân viên, không phòng ban, không thông tin liên hệ. HR không trả về chúng, nên chúng không thể rò vào token.
5. Claim bị cấm
| Claim | Vì sao |
|---|---|
citizenId, cccd, citizen_id, nationalId |
Dữ liệu cá nhân nhạy cảm. Chỉ là đầu vào để xác minh. |
| Payload gốc của HR dưới bất kỳ khoá nào | Nguyên tắc lộ tối thiểu — token mang một phán quyết, không mang một hồ sơ. |
| Mật khẩu, credential hay secret bất kỳ | |
| Bất cứ thứ gì phân biệt "không có nhân viên này" với "sai CCCD" | Dò tìm danh sách. |
6. Audience — một vấn đề còn mở
Public client của Keycloak mặc định nhận aud: ["account"]; audience mapper thêm client id vào. Entitlement Service hiện chấp nhận wifi-portal-api, wifi-portal-employee-api, hai redirect client cũ và account, và lùi về dùng azp khi aud quá mỏng.
Chấp nhận account là rộng hơn mức cần thiết: bất kỳ token nào trong realm cũng qua được kiểm tra audience. Điều này chấp nhận được trong một realm POC, và không chấp nhận được trong production.
Production: TBC (T-AUD). Đích đến là mỗi resource server một audience riêng (giw-entitlement, giw-session) và từ chối account.
7. Claim đến từ đâu
HR Verification API
employeeRef, company, active
│
▼
Custom Authenticator ── ghi user attribute ──▶ Keycloak user
user_type = EMPLOYEE │
employee_verified = true │
employee_ref, company │
(citizenId bị bỏ ở đây) ▼
client scope giw-identity
(4 mapper attribute→claim)
│
▼
Access token
Client scope được tạo bởi bootstrap/src/bootstrap.js, không phải bởi realm-export.json — đưa một mảng clientScopes vào bản import realm đầy đủ sẽ âm thầm thay thế các scope dựng sẵn của Keycloak và tước mất profile/email/roles/basic khỏi mọi token. Lỗi đó đã xảy ra thật và đã được sửa trong POC này.