> ## Documentation Index
> Fetch the complete documentation index at: https://docs.neobank.proofof.tech/llms.txt
> Use this file to discover all available pages before exploring further.

# Identity model

> Auth0 backend-for-frontend sessions for customers and operators, and scoped OAuth2 bearer tokens for agents.

# Identity model

The platform has three principal types — **customer**, **agent / internal rule**, and **operator** — each
with a distinct authentication path, authority model, and audit footprint. All of them flow through
`current_principal` in `apps/api/src/neobank/security.py` and land in the same frozen `Principal` value.

## Auth0 backend-for-frontend (BFF)

The customer browser must never hold an identity token or a Perflo bearer token. The API acts as a
confidential Auth0 client and runs the Authorization Code flow with PKCE itself:

* The API owns the Authorization Code + PKCE exchange; the browser only ever sees an **opaque, HTTP-only
  session cookie** (`__Host-neobank_session` in secure runtimes).
* Session state lives in Redis, keyed by that opaque cookie — no JWT in browser storage.
* Cross-site request forgery protection is enforced on every unsafe method: the `Origin` header must equal
  the public origin and `X-CSRF-Token` must match the session
  (`_validate_csrf`).
* Sensitive writes require **fresh multifactor step-up**. A recent MFA session can confirm a new intent
  without another redirect, but it cannot authorize a different payload.

Because Auth0 and Perflo tokens stay server-side, browser compromise cannot directly read them. The
customer frontend talks only to the neobank API.

## The three principal types

| Principal                  | Auth path                                                       | Authority                                                 | Identifiers                         |
| -------------------------- | --------------------------------------------------------------- | --------------------------------------------------------- | ----------------------------------- |
| **customer**               | Auth0 BFF cookie session                                        | Role-based + CSRF + fresh-MFA step-up on sensitive writes | `subject`, roles, `auth_time`       |
| **agent / internal\_rule** | OAuth2 Bearer JWT (Authorization Code + PKCE, rotating refresh) | Scope-based, bound to a mandate                           | `sub` + `azp` / `client_id`, scopes |
| **operator**               | Separate Auth0 client, cookie session, `operator` role          | View and suspend only                                     | `subject`, `operator` role          |

### customer

A retail end-user. Authenticated by the BFF cookie plus a valid CSRF token. Writes that touch money —
transfers, mandates, cards, provider linking — go through `require_step_up`, which demands
`has_fresh_step_up`: an MFA, WebAuthn, or passkey authentication method within the freshness window.

### agent / internal\_rule

An autonomous OAuth client acting on a customer's delegated authority. It presents a Bearer JWT validated
against the Auth0 JWKS (`JwksVerifier`, RS256, audience- and issuer-bound). Authority is scope-based
(`require_scope`) and always tied to a specific `client_id` (`azp` or `client_id` claim) and to a customer
mandate. Operations and audit records retain both the customer `subject` and the OAuth `client_id`, so a
different client cannot read the result and operators can attribute every delegated action.

### operator

A human reviewer using the operator console at `/ops`. Operators authenticate through a **separate Auth0
application** with mandatory MFA, carry the `operator` role, and are limited to view and suspend. The
customer login cannot accept the `operator` role, and operator sessions must originate from the dedicated
operator client — so role leakage in the customer application can never open the restricted console.

## Step-up freshness

```python theme={null}
STEP_UP_MAX_AGE_SECONDS = 300
```

`has_fresh_step_up` is `true` only when the principal has an MFA-class authentication method **and** the
time since `auth_time` is within this 300-second window. Outside the window, a sensitive write raises
`StepUpRequired` and forces a fresh MFA redirect. The window is short on purpose: step-up authorizes one
transaction, not a session.

## Confirmation intents

Step-up confirms **intents**, not requests. Each confirmation intent is bound to the customer, the action,
and a canonical payload hash; it expires after ten minutes and can be consumed once. The Auth0 step-up
callback confirms the intent only after matching the customer subject and MFA claim. A fresh MFA session
can confirm a new intent without another redirect, but it cannot authorize a different payload — so a
replayed or tampered confirmation is rejected, not silently re-approved.

<Note title="Debug identity is development-only">
  In `development` and `test` with `NEOBANK_DEBUG_AUTH_ENABLED=true`, the `X-Debug-*` headers synthesize a
  `Principal` directly, including `X-Debug-AMR: mfa` for step-up. Every secure runtime forbids this. See
  [Quickstart](get-started/quickstart) for the header set.
</Note>

## Where to go next

<CardGroup cols="2">
  <Card title="Authentication" icon="lock" href="integrate/authentication">
    Integrating customer sessions and agent tokens against the API.
  </Card>

  <Card title="Agent mandates" icon="user-shield" href="integrate/agent-mandates">
    Binding an OAuth client to one recipient and explicit limits.
  </Card>

  <Card title="Confirmation and idempotency" icon="key" href="integrate/confirmation-idempotency">
    How step-up, confirmations, and idempotency keys compose on sensitive writes.
  </Card>

  <Card title="Adapter modes" icon="layers" href="architecture/adapters">
    The runtime boundary that decides which auth backends are allowed.
  </Card>
</CardGroup>
