> ## 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](/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>
