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

# Best practices

> Token handling, security, performance, and common integration flows for the IAM APIs.

## Token management

1. **Validate JWTs locally.** Fetch JWKS from `/.well-known/jwks.json`, cache it, and verify tokens in your backend to avoid a round trip on every request.

2. **Refresh before expiry.** Access tokens default to 15 minutes. Refresh about 30–60 seconds before expiry to avoid transient 401s.

3. **Treat refresh tokens as single-use.** Each `/refresh` call invalidates the previous refresh token. Always persist the latest pair.

4. **Handle revocation.** A 401 on a formerly valid token can mean password reset or admin revocation. Send the user back through login.

## Security

5. **Never ship API keys in client code.** Proxy requests through your backend or serverless layer so only your servers add `X-API-Key`.

6. **Protect refresh tokens.** Prefer `httpOnly` cookies on the web; use platform secure storage on mobile. Do not store refresh tokens in `localStorage` in the browser.

7. **Model the TOTP two-step flow.** When `/login` returns `requires_totp: true`, collect a TOTP code and call `/login/totp` before you have tokens. Login tickets expire in **5 minutes**.

## Performance

8. **Cache JWKS with rotation in mind.** Cache for 1–24 hours. On verification failure with an unknown `kid`, refresh JWKS.

9. **Do not call `/me` on every request.** It hits the database. Prefer reading `sub`, roles, permissions, and `email_verified` from the JWT.

10. **Use `last_login_method` from login responses.** You can persist it on the client for UX hints without calling `/me`.

## Error handling

11. **Treat 401 and 403 differently.** 401 means bad or missing credentials; 403 means good credentials but policy or state blocks access.

12. **Backoff on 429.** Use the `RateLimit` header’s `reset` value to wait until the window resets.

13. **Avoid email enumeration.** `/forgot-password` always returns 204. Login uses the same error for wrong email and wrong password.

## Integration flows

### Registration with email verification

```text theme={null}
POST /register → requires_email_verification: true
(user receives OTP)
POST /verify-email { email, otp }
POST /login { email, password } → tokens
```

### Login with TOTP

```text theme={null}
POST /login → requires_totp: true, login_ticket
POST /login/totp { login_ticket, totp_code } → tokens
```

### Token lifecycle

```text theme={null}
POST /login → access_token + refresh_token
(access token expires, default 15 minutes)
POST /refresh { refresh_token } → new pair
POST /logout { refresh_token }
```

### Last login method hint (browser example)

```javascript theme={null}
// After successful login
const { access_token, refresh_token, last_login_method } = response;
if (last_login_method) {
  localStorage.setItem('last_login_method', last_login_method);
}

// On login page load
const lastMethod = localStorage.getItem('last_login_method');
```

## JWT claims reference

Access tokens are RS256-signed. Typical claims and header fields:

| Claim / field    | Description            |
| ---------------- | ---------------------- |
| `sub`            | User ID (UUID)         |
| `pid`            | Project ID (UUID)      |
| `roles`          | Role slugs             |
| `permissions`    | Permission slugs       |
| `email_verified` | Boolean                |
| `kid` (header)   | Key ID for JWKS lookup |
| `exp`            | Expiry (unix time)     |
| `iat`            | Issued-at (unix time)  |
