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