# Authentication Middleware and Token Normalization

# Authentication Middleware and Token Normalization

<details>
<summary>Relevant source files</summary>

The following files were used as context for generating this wiki page:

- [src/modules/auth/auth.middleware.ts](src/modules/auth/auth.middleware.ts)
- [src/modules/auth/session-context.ts](src/modules/auth/session-context.ts)
- [src/modules/auth/token-normalizer.ts](src/modules/auth/token-normalizer.ts)
- [src/modules/auth/types.ts](src/modules/auth/types.ts)

</details>



The authentication system in Primebrick v3 is designed to be identity-provider (IDP) agnostic, supporting both direct JWT validation and upstream API gateway trust modes. It ensures that regardless of the authentication source, downstream business logic receives a consistent `AuthUser` object mapped to an internal system UUID.

## Authentication Modes

The middleware supports two distinct execution paths configured via the environment, converging into a single internal identity model [src/modules/auth/auth.middleware.ts:20-21]().

### 1. STANDALONE Mode
In this mode, the backend is responsible for verifying the authenticity of the credentials. It attempts to extract a JWT from two locations:
1.  **Authorization Header**: `Bearer <jwt>` [src/modules/auth/auth.middleware.ts:99-102]().
2.  **HttpOnly Cookie**: `access_token` (used primarily by the Svelte frontend) [src/modules/auth/auth.middleware.ts:105-107]().

The token is verified using `openid-client` and JWKS (JSON Web Key Sets) fetched from the IDP's OIDC discovery endpoint [src/modules/auth/auth.middleware.ts:8-9](), [src/modules/auth/auth.middleware.ts:118-119]().

### 2. GATEWAY Mode
In this mode, the backend trusts an upstream gateway (e.g., Kong, Tyk, APISIX) that has already performed authentication. 
*   **Anti-Spoofing**: To prevent direct access to the API bypassing the gateway, a shared secret (`X-Gateway-Secret`) must be provided in the headers [src/modules/auth/auth.middleware.ts:147-154]().
*   **Header Mapping**: User identity, roles, and organization info are read directly from headers injected by the gateway [src/modules/auth/auth.middleware.ts:156-176]().

### Auth Flow and Entity Mapping
The following diagram illustrates how external credentials (JWT or Headers) are transformed into the internal `AuthUser` type and propagated through the system.

**Auth Data Flow: From Request to Session**
```mermaid
graph TD
    subgraph "Entry Points"
        [req_headers] --> |"Bearer JWT"| STANDALONE["fromStandalone()"]
        [req_cookies] --> |"access_token"| STANDALONE
        [gateway_headers] --> |"X-User-*"| GATEWAY["fromGateway()"]
    end

    STANDALONE --> |"JWT Claims"| TN["normalizeIdpToken()"]
    GATEWAY --> |"Header Strings"| TN

    subgraph "Normalization & Enrichment"
        TN --> |"NormalizedIdpUser"| UPR["resolveInternalUuid()"]
        UPR --> |"internalUuid"| EP["expandPermissions()"]
        EP --> |"permissions + isAdmin"| BAU["buildAuthUser()"]
    end

    BAU --> |"AuthUser"| REQ["req.user"]
    REQ --> |"Session"| ALS["runWithSession()"]
    
    Sources["Sources: src/modules/auth/auth.middleware.ts:68-88, src/modules/auth/token-normalizer.ts:69-134"]
```

## Token Normalization

Different IDPs (Casdoor, Keycloak, Entra ID) structure JWT claims differently, especially roles. The `token-normalizer.ts` provides a robust mechanism to flatten these differences.

### `AUTH_ROLES_PATH`
The system uses a configurable dotted path (e.g., `realm_access.roles` for Keycloak or `roles` for Casdoor) to locate role data within the JWT [src/modules/auth/token-normalizer.ts:10-12](). The `readPath` function traverses the JSON payload to extract this data [src/modules/auth/token-normalizer.ts:24-31]().

### Role Coercion
The `coerceRoles` function handles various role formats:
*   Flat string arrays: `["admin", "user"]`
*   Object arrays: `[{ "name": "admin" }]`
*   Mixed or null values are safely stringified or filtered [src/modules/auth/token-normalizer.ts:40-51]().

### Internal UUID Resolution
A critical security boundary is the separation of the IDP's `sub` (subject) claim from the internal system ID.
*   **`idp_code`**: The original JWT `sub` is stored for traceability [src/modules/auth/types.ts:19-20]().
*   **`id`**: The `resolveInternalUuid` function maps the `idp_code` to a local `user_profiles.uuid` [src/modules/auth/auth.middleware.ts:130-136](). If the user does not exist, they are Just-In-Time (JIT) provisioned.

Sources: [src/modules/auth/token-normalizer.ts:1-134](), [src/modules/auth/auth.middleware.ts:129-142]().

## Session Context Propagation

Primebrick uses `AsyncLocalStorage` (ALS) to provide the authenticated context to downstream services without polluting every function signature with a `user` parameter [src/modules/auth/session-context.ts:4-9]().

### `AuthUser` vs `Session`
*   **`AuthUser`**: Attached to `req.user` for Express-level access [src/modules/auth/types.ts:16-32]().
*   **`Session`**: A subset of `AuthUser` mirrored into ALS [src/modules/auth/session-context.ts:42-76]().

### Usage in Code
*   **Middleware Setup**: The `authMiddleware` calls `runWithSession(session, () => next())` to wrap the entire request lifecycle [src/modules/auth/auth.middleware.ts:81-88]().
*   **DAL/Repository Access**: Data access layers call `requireActor()` to retrieve the current user's UUID for audit columns like `created_by` or `updated_by` [src/modules/auth/session-context.ts:104-114]().
*   **System Operations**: For background jobs or migrations where no HTTP request exists, `runAsSystem()` provides a synthetic session with the actor set to `"system"` [src/modules/auth/session-context.ts:130-135]().

**Entity Mapping: Code Structures**
| Code Entity | Purpose | File |
| :--- | :--- | :--- |
| `AuthUser` | Interface for the user attached to `req.user` | [src/modules/auth/types.ts:16]() |
| `Session` | Interface for data stored in `AsyncLocalStorage` | [src/modules/auth/session-context.ts:42]() |
| `normalizeIdpToken` | Logic for extracting user data from IDP-specific JWTs | [src/modules/auth/token-normalizer.ts:69]() |
| `loadRoleMappings` | Loads `role_mappings` table into a memory cache | [src/modules/auth/auth.middleware.ts:51]() |
| `requireActor` | Helper to get the current actor UUID from the session | [src/modules/auth/session-context.ts:104]() |

Sources: [src/modules/auth/session-context.ts:1-136](), [src/modules/auth/auth.middleware.ts:1-93]()

---
