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.

GIW POC Identity Platform · local demo · not production · generated from the repository