Tổng quan API — GIW POC Identity Platform
Trạng thái: POC · Phiên bản: 0.1.0 · Ngày: 21/09/2026
Đặc tả: openapi.yaml (OpenAPI 3.0.3, đã validate)
1. Nền tảng này chứng minh điều gì
Bốn điều, chạy trọn luồng, trên một máy tính xách tay:
- Khách hàng đăng ký, đăng nhập và đăng xuất với Keycloak, nhận về một phiên OIDC.
- Cán bộ nhân viên được xác thực bằng mã nhân viên + CCCD, trong đó Keycloak — không phải portal — gọi HR Verification API và chỉ cấp phiên khi MATCH + ACTIVE.
- Sau cả hai đường, Wi-Fi Portal lấy quyết định từ Entitlement Service, rồi mới yêu cầu Session Service mở truy cập.
- CCCD không bao giờ lọt vào token, log, bản ghi cơ sở dữ liệu hay thông báo lỗi.
2. Kiến trúc API — portal làm chủ giao diện
Không có trang nào do Keycloak render trong nền tảng này. Wi-Fi Portal tự phục vụ màn hình của nó và gọi Galaxy ID qua token API và admin API.
Browser ──▶ Portal API ──┬──▶ Galaxy ID (Keycloak) xác thực
(sid cookie) ├──▶ Entitlement Service quyết định
└──▶ Session Service thực thi
│
Keycloak ──────┴──▶ HR Verification API
Trình duyệt chỉ nói chuyện với đúng một host — portal — và giữ đúng một cookie mờ. Nó không bao giờ thấy token, không biết địa chỉ Keycloak, cũng không biết địa chỉ của bất kỳ hệ thống phía sau nào.
Vì sao không dùng luồng redirect
Authorization Code redirect là câu trả lời trong sách giáo khoa, và ở đây nó sai:
- Captive network assistant (trình duyệt mini mà iOS và macOS bật lên trên Wi-Fi captive) xử lý redirect sang origin bên thứ ba rất tệ và không giữ cookie đáng tin. Đăng nhập kiểu redirect hỏng đúng ở nơi sản phẩm này sống.
- Tính liền mạch thương hiệu: đẩy hành khách Vietjet sang một trang Keycloak rồi quay lại là một vết nối nhìn thấy được trong hành trình 30 giây.
Cái giá phải trả, nói thẳng
| Mất gì | Hệ quả |
|---|---|
| SSO giữa các sản phẩm Galaxy | Mỗi lần đăng nhập portal là độc lập |
| MFA / step-up | Không có cách chuẩn nào để thử thách thêm bên trong password grant |
| Liên kết IdP (doanh nghiệp, mạng xã hội) | Sẽ phải thêm lại một đường redirect |
| Thông tin đăng nhập không đi qua portal | Portal giờ nằm trong phạm vi audit về xử lý credential |
Đây không phải là một mẫu được khuyến nghị. RFC 9700 (OAuth 2.0 Security Best Current Practice) nói password grant MUST NOT be used, và không ghi nhận ngoại lệ first-party nào. Direct Grant chỉ được dùng như một giải pháp tạm thời có giới hạn cho POC, nhằm kiểm chứng trải nghiệm captive portal. Kiến trúc xác thực cho production vẫn TBC và nên ưu tiên OIDC/federation chạy trên trình duyệt khi điều kiện vận hành cho phép.
Những gì không thay đổi, và không được phép thay đổi:
- Keycloak vẫn ra mọi quyết định xác thực. Portal không có kho mật khẩu, không có credential của HR.
- Keycloak vẫn gọi HR để xác minh nhân viên, qua một direct-grant authenticator tuỳ biến. Portal chỉ chuyển tiếp hai ô trên form.
- Token nằm lại phía server.
Chuẩn sẵn có và phần Galaxy tự xây
| Chặng | Bản chất | Đặc tả ở đâu |
|---|---|---|
| Browser ↔ Wi-Fi Portal | Galaxy tự xây | openapi.yaml → /api/v1/auth/* |
| Wi-Fi Portal ↔ Galaxy ID | CHUẨN OAuth 2.0 / OIDC — RFC 6749 §4.3 password grant, RFC 6749 §4.4 client credentials, RFC 7009 logout, Keycloak Admin REST | Discovery document của Keycloak. Không đặc tả lại trong openapi.yaml. |
| Keycloak ↔ HR Verification API | Galaxy tự xây | openapi.yaml → POST /api/v1/employees/verify |
| Wi-Fi Portal ↔ Entitlement Service | Galaxy tự xây | openapi.yaml → POST /api/v1/entitlements/evaluate |
| Wi-Fi Portal ↔ Session Service | Galaxy tự xây | openapi.yaml → /api/v1/sessions* |
Các endpoint chuẩn đang dùng, chỉ để tham chiếu:
| Mục đích | Endpoint (realm galaxy-id-poc) |
Client |
|---|---|---|
| Đăng nhập khách hàng | POST /protocol/openid-connect/token grant_type=password |
wifi-portal-api |
| Xác minh nhân viên | POST /protocol/openid-connect/token grant_type=password + employeeId + citizenId |
wifi-portal-employee-api |
| Refresh | POST /protocol/openid-connect/token grant_type=refresh_token |
cả hai |
| Service token (đăng ký) | POST /protocol/openid-connect/token grant_type=client_credentials |
wifi-portal-api |
| Tạo tài khoản | POST /admin/realms/{realm}/users |
service account, chỉ manage-users |
| Back-channel logout | POST /protocol/openid-connect/logout |
cả hai |
| JWKS | GET /protocol/openid-connect/certs |
Entitlement Service |
Luồng nhân viên không có endpoint riêng. Nó vẫn là token endpoint thông thường, trên một confidential client thứ hai mà flow direct_grant bị ghi đè thành employee-direct-grant. Authenticator của Keycloak đọc employeeId và citizenId từ request rồi gọi HR.
3. Phân tách trách nhiệm
Keycloak danh tính và xác thực BẠN LÀ AI
HR Verification tình trạng lao động CÓ PHẢI NHÂN VIÊN, CÒN LÀM VIỆC KHÔNG
Entitlement quyết định quyền truy cập ĐƯỢC DÙNG GÌ
Session / Network thực thi quyền truy cập BẬT LÊN
Hai quy tắc rút ra, và cả hai được cưỡng chế bằng code chứ không chỉ bằng văn bản:
- Keycloak không bao giờ mở mạng. Nó phát token rồi dừng. Portal phải tự đi lấy entitlement.
- Session Service không bao giờ quyết định.
POST /sessions/{id}/granttrả422 ENTITLEMENT_DENIEDnếu không cóentitlementId(ADR-008).
4. Các thành phần
| Service | Cổng | Công nghệ | Vai trò |
|---|---|---|---|
keycloak |
8080 (9000 health) | Keycloak 26.7.4 + custom Java SPI | Galaxy ID. Có cả browser flow lẫn direct-grant flow cho nhân viên |
postgres-keycloak |
— | Postgres 16 | Lưu trữ cho Keycloak |
mock-hr-api |
3001 | Node 22, không dependency | Đóng vai hệ thống nhân sự gốc |
entitlement-service |
3002 | Node 22, không dependency | Verify token, áp policy, cấp entitlement |
session-service |
3003 | Node 22, không dependency | Mô phỏng các động từ của Network Adapter theo ADR-007 |
wifi-portal |
3000 | Node 22, không dependency | Phục vụ giao diện, gọi Galaxy ID, điều phối entitlement và session |
bootstrap |
— | Node 22 | Chạy một lần: client scope, flow binding, user test |
Các service Node không có dependency npm nào, có chủ đích. Một POC xử lý CCCD thì phải đọc kiểm được từng dòng, không phải lần theo một cây dependency bắc cầu.
5. Liên kết danh tính — chiến lược cho POC
Khi một nhân viên xác thực, authenticator tuỳ biến tìm user Keycloak theo thứ tự này:
- User mang attribute
employee_refbằng đúng reference mà HR trả về. - Nếu không có, user có username là reference viết thường (
emp-12345). - Nếu vẫn không có, tạo user mới.
Nếu bước 1 tìm thấy nhiều hơn một user cùng employee_ref, luồng thất bại với IDENTITY_LINK_CONFLICT thay vì đoán.
Cố ý không làm: tự động gộp với một tài khoản CUSTOMER đã có, khớp theo email hoặc số điện thoại. Gộp hai danh tính một cách im lặng là việc không thể hoàn tác, và là quyết định cho production chứ không phải tiện tay trong POC. Theo dõi ở T-LINK §8.
Production: TBC. Cần quyết định Galaxy ID giữ một subject với hai vai trò, hay hai subject liên kết nhau. Liên quan ADR-002.
6. Correlation
Mọi request đều mang hoặc tự sinh X-Correlation-ID, và nó được trả lại trên mọi response. Một id đi suốt:
Portal → Keycloak → Custom Authenticator → HR API → Entitlement → Session
Sinh theo dạng corr-<uuid4>. Nó không bao giờ được dẫn xuất từ CCCD, mã nhân viên hay bất kỳ dữ liệu cá nhân nào — một correlation id sẽ nằm trong log và dashboard, đó là thiết kế.
7. Giả định của POC
Mọi thứ trong bảng này là phỏng đoán đặt ra để dựng được stack. Không có gì là SLA production.
| Giả định | Giá trị | Đặt ở đâu |
|---|---|---|
| Timeout gọi HR verify | 3000 ms | GIW_HR_TIMEOUT_MS |
| Số lần gọi HR | 2 (một lần thử lại) | GIW_HR_MAX_ATTEMPTS |
| TTL entitlement nhân viên | 4 h | TTL_EMPLOYEE_SEC |
| TTL entitlement khách hàng | 1 h | TTL_CUSTOMER_SEC |
| Vòng đời access token | 900 s | cấu hình realm |
| Timeout Portal → Galaxy ID | 5000 ms | idp.js |
| Timeout Portal → Galaxy ID, luồng nhân viên | 12000 ms | IDP_EMPLOYEE_TIMEOUT_MS. Phải lớn hơn hrTimeoutMs × hrMaxAttempts, nếu không portal hết giờ trước và đổ lỗi cho Galaxy ID về một sự cố của HR. |
| TTL phiên portal | 3600 s, dọn mỗi 5 phút | server.js |
| SSO idle / max | 1800 s / 36000 s | cấu hình realm |
| Số lần submit form mỗi phiên xác thực | 5 | cấu hình authenticator |
| Giới hạn tần suất gọi HR | 30 req / 60 s mỗi peer | RATE_LIMIT_MAX |
| Công ty đủ điều kiện | VIETJET, GALAXY, SOVICO | EMPLOYEE_COMPANIES |
8. Câu hỏi còn mở
| Ref | Câu hỏi | Chặn cái gì |
|---|---|---|
| T-CCCD | CCCD có được chấp nhận làm đầu vào xác minh cho production hay không? ADR-003 và quyết định D3 hiện nói bỏ nó đi; POC này vẫn làm vì task order yêu cầu. | Luồng nhân viên production |
| T-LINK | Một subject hai vai trò, hay hai subject liên kết, khi một nhân viên đồng thời là khách hàng? | ADR-002, mô hình user |
| T-TIER | BASIC/BOOST có phải là hạng thật không, và đòn bẩy là thời lượng hay tốc độ? | ADR-011, danh mục entitlement |
| T-HR-SOR | Hệ thống nào mới là hệ nhân sự gốc thật, và hợp đồng, mô hình xác thực, SLA thực tế của nó là gì? | Toàn bộ chặng Keycloak ↔ HR |
| T-AUD | Mỗi service có nên có audience riêng thay vì chấp nhận account không? |
API-SECURITY.md §3 |
| T-GRANT | Password grant có chấp nhận được lâu dài không, hay portal nên chuyển sang device/CIBA flow, hoặc thêm một đường redirect cho federation doanh nghiệp? | §2 ở trên, ADR-002 |
| T-MFA | Nếu sau này bắt buộc MFA, password grant không mang được step-up. Thay bằng gì? | §2 ở trên |
| T-STORE | Trạng thái entitlement và session đang nằm trong bộ nhớ. Kho lưu trữ production là gì và giữ bao lâu? | DATA-CLASSIFICATION.md |
9. Thứ tự đọc cho người review
- Tài liệu này
API-MATRIX.md— mọi thứ trên một trangAPI-SEQUENCES.md— ba luồng dưới dạng sơ đồTOKEN-CLAIMS.md— hợp đồng tokenDATA-CLASSIFICATION.md— từng trường dữ liệu sống ở đâu và chết ở đâuAPI-SECURITY.md— các biện pháp, và những gì còn thiếuERROR-CATALOG.mdAPI-REVIEW-CHECKLIST.md— mang phản biện của bạn tới đây