API Sequences — GIW POC Identity Platform
Status: POC · Date: 2026-09-21
All flows are API calls. The browser never leaves the portal and never sees a Keycloak page. Step numbers correspond to log lines you can follow with a single X-Correlation-ID.
1. Customer login
sequenceDiagram
autonumber
actor U as Passenger
participant B as Portal frontend
participant P as Portal API
participant K as Galaxy ID (Keycloak)
participant E as Entitlement Service
participant S as Session Service
U->>B: opens the captive portal
B->>P: GET /api/v1/auth/session
P-->>B: authenticated false
B-->>U: entry screen, 3 CTAs
U->>B: Đăng nhập Galaxy ID, submits the form
B->>P: POST /api/v1/auth/login {username, password}
P->>K: POST /token grant_type=password<br/>(confidential client + secret)
Note over P,K: The portal has no password store.<br/>Keycloak decides, not the portal.
K-->>P: access_token, refresh_token, id_token
P->>E: POST /entitlements/evaluate (Bearer, Idempotency-Key)
E->>K: GET /certs (JWKS, cached 5 min)
E->>E: verify sig, iss, aud, exp then apply policy
E-->>P: 201 WIFI_CUSTOMER_BASIC · tier BASIC
P->>S: POST /sessions (Idempotency-Key)
S-->>P: 201 state CREATED
P->>S: POST /sessions/{id}/grant (entitlementId)
S-->>P: 200 state GRANTED
P-->>B: 200 + Set-Cookie sid (opaque, HttpOnly, SameSite=Strict)
Note over B: Tokens stay on the server.<br/>The browser holds only the cookie.
B-->>U: Access Granted screen
Entitlement before session, always. A session created before a decision is a session that can be granted by accident.
2. Registration
sequenceDiagram
autonumber
actor U as Passenger
participant B as Portal frontend
participant P as Portal API
participant K as Galaxy ID (Keycloak)
U->>B: Đăng ký Galaxy ID
B->>P: POST /api/v1/auth/register {email, password, displayName}
P->>P: validate email shape, password length
Note over P: Rejected here means Galaxy ID<br/>is never troubled with it.
P->>K: POST /token grant_type=client_credentials
K-->>P: service-account token
Note over P,K: Service account is scoped to manage-users.<br/>Deliberately not realm-admin.
P->>K: POST /admin/realms/{realm}/users
alt created
K-->>P: 201
P->>K: POST /token grant_type=password
K-->>P: tokens
Note over P: Register-then-login: exactly one<br/>code path mints a session.
P-->>B: 200 (entitlement + session, as in flow 1)
else already exists
K-->>P: 409
P-->>B: 409 ACCOUNT_EXISTS
end
3. Employee verification
sequenceDiagram
autonumber
actor U as Employee
participant B as Portal frontend
participant P as Portal API
participant K as Galaxy ID (Keycloak)
participant A as Direct-grant authenticator
participant H as HR Verification API
participant E as Entitlement Service
participant S as Session Service
U->>B: Đăng nhập CBNV
B-->>U: portal-rendered form (Employee ID + CCCD)
U->>B: submits
B->>P: POST /api/v1/auth/employee {employeeId, citizenId}
P->>P: presence check only
P->>K: POST /token grant_type=password<br/>+ employeeId + citizenId
Note over P,K: CCCD travels as its own field,<br/>never as the OAuth password parameter.
K->>A: direct_grant flow = employee-direct-grant
A->>A: validate format
A->>H: POST /employees/verify (X-API-Key, X-Correlation-ID)
H->>H: constant-time compare across all records
H-->>A: verified, active, EMP-12345, VIETJET, eligible
Note over A: citizenId leaves scope here.<br/>Never stored, logged or tokenised.
A->>K: find or create user by employee_ref
A->>K: set user_type, employee_verified, employee_ref, company
K-->>P: tokens with employee claims
P->>E: POST /entitlements/evaluate
E-->>P: 201 WIFI_EMPLOYEE_PACKAGE · BOOST · 2 devices
P->>S: POST /sessions then /grant
S-->>P: 200 GRANTED
P-->>B: 200 + Set-Cookie sid
B-->>U: Access Granted
The portal is a relay, not a decision maker. It holds no HR credential and could not verify anyone if it wanted to.
4. Logout
sequenceDiagram
autonumber
actor U as Passenger
participant P as Portal API
participant S as Session Service
participant K as Galaxy ID
U->>P: POST /api/v1/auth/logout (sid cookie)
P->>S: POST /sessions/{id}/revoke
S-->>P: 200 REVOKED
Note over P,S: Revoke first. If the IdP call then<br/>fails, access is already off.
P->>K: POST /logout (back-channel, refresh_token)
P->>P: drop the server-side session
P-->>U: 200 + clear the sid cookie
5. Failure flows
5a. HR timeout or outage
sequenceDiagram
autonumber
participant P as Portal API
participant K as Galaxy ID
participant A as Direct-grant authenticator
participant H as HR Verification API
P->>K: POST /token (+ employeeId, citizenId)
K->>A: employee-direct-grant
A->>H: attempt 1 (timeout 3000 ms)
H--xA: no response
Note over A: Retry is safe — verify is read-only.
A->>H: attempt 2
H--xA: no response
A-->>K: 504 temporarily_unavailable
K-->>P: 504
P-->>P: map to HR_SERVICE_TIMEOUT
Note over P: No session. No user provisioned.<br/>No partial access.
The portal's own budget for this call is 12 s, deliberately larger than 3000 ms × 2 attempts. Set it lower and the portal times out first, reporting "Galaxy ID is down" when the real fault is HR — a support team would then chase the wrong system.
A 401/403 from HR is not retried: bad service credentials will not fix themselves, and it surfaces as "unavailable" rather than leaking that the key was rejected.
5b. Wrong credentials, unknown account, or a locked account
sequenceDiagram
autonumber
participant B as Portal frontend
participant P as Portal API
participant K as Galaxy ID
B->>P: POST /api/v1/auth/login
P->>K: POST /token grant_type=password
alt wrong password
K-->>P: 401 invalid_grant
else no such account
K-->>P: 401 invalid_grant
else brute-force lockout in effect
K-->>P: 401 invalid_grant
end
P-->>B: 401 INVALID_CREDENTIALS<br/>"Thông tin đăng nhập không đúng."
Note over K,B: Three different causes, one identical answer.<br/>Anything else is an enumeration oracle.
5c. Employee: not found, wrong CCCD, inactive, ineligible
sequenceDiagram
autonumber
participant P as Portal API
participant A as Direct-grant authenticator
participant H as HR Verification API
P->>A: employeeId + citizenId
A->>H: POST /employees/verify
H->>H: scan every record, constant-time, uniform work
alt no match (unknown id OR wrong CCCD)
H-->>A: verified false, NOT_FOUND_OR_MISMATCH
A-->>P: 401 invalid_grant → INVALID_CREDENTIALS
Note over H,P: One identical answer for both.<br/>No timing signal either.
else match but INACTIVE
H-->>A: verified true, active false
A-->>P: 403 employee_inactive → EMPLOYEE_INACTIVE
Note over A,P: Only reachable after a CORRECT CCCD,<br/>so naming the reason leaks nothing.
else match, ACTIVE, not wifi eligible
H-->>A: wifiEligible false
A-->>P: 403 access_denied → FORBIDDEN
end
5d. Authenticated but not entitled
sequenceDiagram
autonumber
participant P as Portal API
participant K as Galaxy ID
participant E as Entitlement Service
participant S as Session Service
P->>K: POST /token (+ employeeId, citizenId)
K-->>P: tokens — HR confirmed an ACTIVE, eligible employee
P->>E: POST /entitlements/evaluate (company PARTNER-X)
E->>E: PARTNER-X not in EMPLOYEE_COMPANIES
E-->>P: 403 ENTITLEMENT_DENIED · COMPANY_NOT_ELIGIBLE
P-->>P: no session is created at all
Note over P,S: Authentication succeeded.<br/>Access did not.<br/>These are different questions.
This is the flow worth showing a sponsor. Test case: PTX10001 / 001234500005.