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

# Security

> Password hashing, JWT signing, JWE encryption, refresh-token rotation, rate limiting, account lockout, and key rotation.

This page is the security reference for Archie Auth — the cryptography under the hood, the rate-limit and lockout defaults, and how to rotate keys without taking users offline.

## Password hashing

Passwords are hashed with **bcrypt** (cost factor 12) before storage. Plaintext passwords are never persisted. Bcrypt's per-hash salt means identical passwords produce different hashes — rainbow tables are useless against the stored values.

### Password policy

Configurable per environment. Defaults:

| Rule | Default |
| - | - |
| Minimum length | 8 characters |
| Uppercase letter | Required |
| Lowercase letter | Required |
| Digit | Required |
| Special character | Required |

Policy violations come back as an `array` in the signup / reset response, so your UI can show targeted feedback ("password needs a special character") rather than a generic error.

## Token strategy

Archie Auth issues a two-tier token pair on every successful authentication:

### Access token

| Property | Value |
| - | - |
| Format | JWT |
| Signing algorithm | RS256 (RSA + SHA-256) |
| Default TTL | 15 minutes |
| Claims | `iss`, `sub` (userId), `aud` (projectId), `exp`, `iat`, `email`, `roles[]`, `environment` |

Sent on every API call as `Authorization: Bearer <token>`. The short lifetime is deliberate — limits how long a stolen token is useful.

### Refresh token

| Property | Value |
| - | - |
| Format | Cryptographically random opaque string |
| Default TTL | 30 days |
| Rotation | Each use issues a new refresh token and invalidates the previous one |

Sent only to `/auth/refresh-token`. **Always store the new refresh token from every refresh response** — the previous one is invalidated immediately. Reusing a previously-rotated refresh token is detected and triggers a session-wide invalidation.

## JWS vs. JWE

Two access-token formats. Pick based on whether the claims need to be confidential.

### JWS — signed, claims visible (default)

A standard 3-part `header.payload.signature` JWT. Claims are readable to anyone who decodes the token; the signature prevents tampering. Validated with the public key from the JWKS endpoint.

This is the right default. Most applications can tolerate readable claims (subject, email, roles).

### JWE — encrypted (opt-in)

A 5-part JWE string. Claims are encrypted and unreadable without the platform's private decryption key. Built on top of JWS: tokens are signed first, then encrypted (nested JWT).

| Algorithm | Used for |
| - | - |
| **RSA-OAEP-256** | Key wrapping. |
| **A256GCM** | Content encryption. |

Enable JWE when claims contain sensitive information that must not be inspectable at the client or in logs, or when compliance mandates strict claim confidentiality.

Toggle **JWE Encryption** in the Archie Auth settings to enable, or call `configureProjectAuth` with `jweEnabled: true`. Both formats are accepted on inbound requests, so you can flip the toggle without breaking in-flight tokens.

## Key management

### Two key pairs

Separate 2048-bit RSA key pairs for signing and encryption. Private keys are encrypted at rest with AES-GCM. Public signing keys are exposed at the standard JWKS endpoint:

```text theme={null}
GET https://your-gateway.example.com/auth/.well-known/jwks.json
```

External services (microservices, edge workers, anything that needs to validate Archie-issued tokens locally) can fetch the JWKS and verify tokens without calling Archie.

### Per-environment keys

Each environment manages its own keys independently. A token signed in `staging` cannot be validated in `master`. That isolation is the security boundary — there's no path by which a `dev` token bleeds into production traffic.

### Key rotation

Rotate from the Settings tab or via the `rotateAuthKeys` mutation. The flow:

1. New key pairs are generated.
2. Old public keys are kept for a **1-hour grace period**, so existing tokens stay valid while clients pick up the new keys.
3. New tokens are signed with the new keys immediately.
4. After the grace period, old keys are dropped.

```graphql theme={null}
mutation {
  rotateAuthKeys(input: { keyType: "both" }) {
    success
    message
  }
}
```

`keyType` accepts `"signing"`, `"encryption"`, or `"both"`. Rotate signing keys regularly; rotate encryption keys when you suspect compromise or on a compliance schedule.

## Rate limiting

Built-in protection on the public auth endpoints:

| Endpoint | Limit | Scope |
| - | - | - |
| `POST /auth/signup` | 10 / minute | Per IP |
| `POST /auth/login` | 20 / minute | Per IP |
| `POST /auth/recover-password` | 5 / minute | Per email |

Exceeding any limit returns `429 Too Many Requests` with a `Retry-After` header. Recovery is per-email (not per-IP) so a single attacker can't burn the rate-limit budget for every user from one address.

## Account lockout

After too many failed logins, an account is temporarily locked to defend against credential stuffing.

| Setting | Default |
| - | - |
| Max Failed Attempts | 5 |
| Lock Duration | 30 minutes |
| Reset on Success | Yes — counter resets on the next successful login. |
| Reset on Password Change | Yes — counter and lock state both clear after password reset. |

Locked accounts return `423 Locked` with a `lockedUntil` timestamp on the next login attempt.

## Token revocation

When a user logs out (or an admin force-logs them out), the access token is added to a distributed blacklist. The blacklist is checked on every request, so a revoked token is rejected before its natural expiry.

Refresh tokens are invalidated synchronously — the database row is removed, so the next refresh attempt fails. There is no grace period on revoked refresh tokens.

## Verification code security

Email verification and password recovery codes:

| Property | Value |
| - | - |
| Format | 6-digit numeric |
| Expiry | 1 hour |
| Max attempts | 5 before the code is invalidated |

Codes are single-use; on success they're deleted. On lockout, the user (or an admin) can request a fresh code.

## Authentication events

Archie Auth emits system events at key points in the lifecycle. Subscribe to them via custom functions or webhooks for audit logging, anomaly detection, or downstream sync.

| Event | When it fires |
| - | - |
| `auth.user.registered` | Successful signup. |
| `auth.user.login.success` | Successful login. |
| `auth.user.login.failed` | Failed login (wrong password, account state, etc.). |
| `auth.user.logout` | Logout (manual or admin force-logout). |
| `auth.user.password.changed` | Password updated. |
| `auth.user.password.recovery` | Password recovery flow initiated. |
| `auth.user.email.verified` | Email confirmed. |
| `auth.user.locked` | Account locked due to failed attempts. |

Event payloads include environment, user ID, email, timestamp, and (for failures) the reason and originating IP.

## FAQ

<AccordionGroup>
  <Accordion title="Should I enable JWE?">
    Most apps don't need it — JWS signing is enough. Enable JWE if your access tokens carry sensitive claims you don't want readable client-side or in logs, or if compliance mandates encrypted claims.
  </Accordion>

  <Accordion title="How often should I rotate keys?">
    On a schedule that matches your threat model — every 90 days is a common cadence. Rotate immediately on any suspected compromise. Rotation is non-disruptive thanks to the 1-hour grace period.
  </Accordion>

  <Accordion title="Where do I tune rate limits and lockout?">
    Lockout (max attempts, lock duration) is configurable per environment via the Archie Auth Settings tab or `configureProjectAuth`. Rate limits on the public auth endpoints are platform defaults — for tighter limits, put a [Custom API](/docs/features/backend/app-services/custom-apis) in front with a per-route rate-limit policy.
  </Accordion>

  <Accordion title="Can external services validate tokens without calling Archie?">
    Yes — fetch the JWKS from `/auth/.well-known/jwks.json` and verify locally. The endpoint is public, the response is cached for 5 minutes, and includes both the signing key (`use=sig`) and, when JWE is enabled, the encryption key (`use=enc`).
  </Accordion>

  <Accordion title="What happens if a refresh token is reused?">
    Reuse is treated as a token-theft signal. The session is invalidated and all associated tokens are revoked. The legitimate user sees a forced re-login on their next refresh attempt.
  </Accordion>
</AccordionGroup>
