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.

GIW POC Identity Platform · bản demo local · không phải production · sinh từ repository