Skip to content

Client Authentication Workflow (Detailed) ​

This document provides an end-to-end view of the client authentication system. Client accounts represent agency customers, are always scoped to a specific agency, and only authenticate against active agencies.

1. Agency-Scoped Registration & Verification ​

Clients register within the context of an agency.

1.1 Initiate Registration ​

  • Client sends email, password, firstName, lastName, and agencyId to POST /api/identity/auth/client/register.
  • The system checks if the email exists within that specific agency.
  • Registration is rejected when the target agency is inactive.
  • A new Client entity is created (isActive: false).
  • Previous unused verification codes for this client are marked as used.
  • A CSPRNG 6-digit code is generated (crypto.randomInt()), hashed via the platform hashing service, and stored.
  • The token tracks attempts to prevent brute force.

1.2 Verify Email ​

  • Client submits the code and device info.
  • The system fetches the latest active token.
  • Rate Limiting & Lockout: Endpoint is protected by @RateLimit (5 req/min per IP via Redis sliding window). If the token exceeds MAX_VERIFICATION_ATTEMPTS (5), it is locked.
  • The input code is validated against the stored hash. During transition, legacy plaintext rows are still accepted.
  • Failed attempts increment the counter. Success activates the client and generates an initial Token Pair tied to a new Session.
  • Verify-email responses include tokens plus actor profile (id, email, firstName, lastName) and the current session entry (session).
sequenceDiagram
    participant C as Client App
    participant A as Auth API
    participant DB as Database

    C->>A: POST /api/identity/auth/client/register {email, agencyId}
    A->>DB: Store Client (scoped to agency)
    A->>A: Generate CSPRNG code
    A->>DB: Store hashed code
    A-->>C: 200 OK (Email Sent)

    C->>A: POST /api/identity/auth/client/verify-email {code}
    A->>DB: Fetch code & check lock status
    alt Valid code
        A->>DB: Activate Client
        A->>A: Generate Session & Tokens
        A-->>C: 200 OK + Tokens + Profile + Session
    end

2. Login, Sessions & OAuth ​

2.1 Standard Login ​

  • The LocalClientStrategy validates the email, password, and requires the agencyId.
  • All login denials (missing agency context, invalid agency, invalid credentials, inactive account) return the same generic structured 401 (IDENTITY.INVALID_CREDENTIALS).
  • Login also fails when the agency exists but is inactive.
  • The system creates a Session (recording IP, browser, and device fingerprint). Clients are allowed up to 10 concurrent sessions (oldest is revoked if exceeded).
  • Login responses include token pair plus actor profile (id, email, firstName, lastName) and the current session entry (session).

2.2 Google OAuth (CSRF Protected) ​

Client OAuth requires linking the authentication to a specific agency.

  • Redirect: The client requests GET /api/identity/auth/client/google?agencyId=.... The server generates a random state token, stores a hashed-key one-time entry in Redis with { agencyId } (TTL: 5 minutes), and passes state to Google.
  • Callback: Google returns code and state. The server atomically consumes (read + delete) the stored state, retrieves the agencyId, and completes authentication securely (mitigating CSRF and state replay).
  • Error Privacy: OAuth misconfiguration/request issues use structured 400 (IDENTITY.GOOGLE_AUTH_INVALID_REQUEST), while provider exchange failures return generic structured 401 (IDENTITY.GOOGLE_AUTH_FAILED) without leaking provider internals.
  • Google callback returns the same enriched login response shape used by password login.
sequenceDiagram
    participant C as Client App
    participant A as Auth API
    participant G as Google
    participant Cache as State Store

    C->>A: GET /api/identity/auth/client/google?agencyId=123
    A->>A: Generate UUID 'state'
    A->>Cache: Save {state: 'abc', agencyId: 123}
    A-->>C: Redirect to Google with state='abc'

    C->>G: Authenticate
    G-->>C: Redirect /api/identity/auth/client/google/callback?code=xyz&state=abc
    C->>A: GET /api/identity/auth/client/google/callback?code=xyz&state=abc
    A->>Cache: Lookup 'abc' -> gets agencyId 123
    A->>A: Exchange code & Login Client
    A-->>C: 200 OK + Tokens

3. Token Rotation & Session Management ​

Clients use the same robust token rotation and session management infrastructure as Users.

3.1 Session Validation ​

Every access token request and refresh token request checks session.isActive. If the session was revoked, the tokens are instantly useless.

3.2 Refresh Token Security ​

  • Refresh tokens are single-use.
  • Atomic updates: When refreshed, the old token's hash is revoked.
  • Reuse Detection: If a revoked refresh token is presented, the system immediately invalidates ALL sessions for that client, logs a warning, and returns IDENTITY.REFRESH_TOKEN_REUSE_DETECTED with a neutral re-authentication message.

3.3 Client Access to Session Management ​

Clients have full access to view and manage their sessions via endpoints protected by @Auth():

  • GET /api/identity/auth/sessions: List active devices.
  • GET /api/identity/auth/sessions/current: Read the current device session payload.
  • POST /api/identity/auth/sessions/revoke: Revoke a specific device.
  • POST /api/identity/auth/sessions/revoke-others: Revoke all sessions except the current one.
  • POST /api/identity/auth/client/change-password: Change password, revoke all other active sessions, revoke current-session refresh tokens, and return a fresh token pair for the current session.
sequenceDiagram
    participant C as Client App
    participant A as Auth API
    participant DB as Database

    C->>A: POST /api/identity/auth/client/change-password
    A->>DB: Update Password
    A->>DB: Revoke all other Client Sessions
    A->>DB: Revoke current-session refresh tokens
    A->>A: Generate fresh token pair for current session
    A-->>C: 200 OK + Tokens