[← Back to Authentication index](index.md)

# JWT authentication
JSON Web Tokens (JWT) allow a client to prove their identity by including a
signed token with every request. The token typically contains the user's ID and
an expiry timestamp. Clients obtain an access/refresh pair from a login endpoint
and then send the access token in the `Authorization` header:
`Authorization: Bearer <access-token>`
To enable JWT support you must provide `ACCESS_SECRET_KEY` and
`REFRESH_SECRET_KEY` values along with a user model. A minimal configuration
looks like:
```
class Config(BaseConfig):
    API_AUTHENTICATE_METHOD = ["jwt"]
    ACCESS_SECRET_KEY = "access-secret"
    REFRESH_SECRET_KEY = "refresh-secret"
    API_USER_MODEL = User
    API_USER_LOOKUP_FIELD = "username"
    API_CREDENTIAL_CHECK_METHOD = "check_password"
```
Token lifetimes default to `360` minutes for access tokens and `2880`
minutes (two days) for refresh tokens. Override these durations with
API_JWT_EXPIRY_TIME <configuration.html#JWT_EXPIRY_TIME> and API_JWT_REFRESH_EXPIRY_TIME <configuration.html#JWT_REFRESH_EXPIRY_TIME> respectively. The
default algorithm is `HS256` (override via
API_JWT_ALGORITHM <configuration.html#JWT_ALGORITHM>). When decoding a
token, flarchitect.authentication.jwt.get_user_from_token resolves the
secret key in this order: explicit argument → `ACCESS_SECRET_KEY` environment
variable → Flask config.

## Hardening options
JWT validation can be tightened with the following settings:
- `API_JWT_ALLOWED_ALGORITHMS`: Restrict verification to a specific set of algorithms (list or comma-separated string). Defaults to the configured algorithm.
- `API_JWT_ISSUER` / `API_JWT_AUDIENCE`: Include and enforce `iss`/`aud` claims during encode/decode.
- `API_JWT_LEEWAY`: Allow small clock skew (in seconds) when validating `exp`/`iat`.
- `API_JWT_ALGORITHM="RS256"`: Use RSA key pairs. Set `ACCESS_PRIVATE_KEY` and `ACCESS_PUBLIC_KEY` (and their `REFRESH_*` equivalents) with PEM strings. For compatibility, a single `ACCESS_SECRET_KEY`/`REFRESH_SECRET_KEY` may be used to verify if public keys are not set, but key pairs are recommended.

## Token rotation and revocation
- Refresh tokens are single‑use. When clients call `POST /auth/refresh` with a valid refresh token, the server revokes the token and issues a new access/refresh pair.
- Deny‑list and auditing: The refresh token store persists `created_at`, `last_used_at`, `revoked`/`revoked_at` and a `replaced_by` pointer to the next token. This provides a clear trail for incident response.
- Programmatic revocation: Administrators can revoke a specific token at any time with `revoke_refresh_token(token)` from `flarchitect.authentication.token_store`.

## Built‑in endpoints
When JWT is enabled, flarchitect registers the following routes:
**`POST /auth/login`**
Accepts JSON `{"username": "<name>", "password": "<password>"}` and
returns an access/refresh token pair and the user's primary key.

**`POST /auth/refresh`**
Accepts JSON `{"refresh_token": "<token>"}` and returns a new access
token. For robustness, a value prefixed with `"Bearer "` is accepted and
normalised (e.g., `"Bearer <token>"`). Invalid refresh JWTs yield `401`;
revoked or expired-in-store tokens return `403`.

**`POST /auth/logout`**
Stateless logout that clears the user context on the server.

**`GET /auth/me`**
Returns the current authenticated user as JSON. This endpoint is available
when a user model is configured and any supported authentication method is
enabled (`jwt`, `basic`, `api_key`, or `custom`). The response uses the
model’s output schema, so field visibility follows your schema settings.
Requires a valid `Authorization` header. The path is configurable via
`API_AUTH_ME_ROUTE` (default `"/auth/me"`). You can disable exposing
this endpoint entirely with `API_EXPOSE_ME=False`.
Clients include the access token with each request using the standard header:
```
Authorization: Bearer <access-token>
```

## Auth routes configuration
The built‑in auth routes register automatically when JWT is enabled. You can
adjust this behaviour via configuration:
- `API_AUTO_AUTH_ROUTES` (bool, default `True`): when `False`, flarchitect
    does not register the default `/auth` routes. This is useful if you want to
    provide your own endpoints.
- `API_AUTH_REFRESH_ROUTE` (str, default `"/auth/refresh"`): path for the
    refresh endpoint. The endpoint accepts `{"refresh_token": "..."}` and returns
    a new access token using the standard response wrapper.
- `API_AUTH_ME_ROUTE` (str, default `"/auth/me"`): path for the current-user
    endpoint. When using `custom` authentication, ensure `API_USER_MODEL` is
    configured so the response can be serialised.
- `API_EXPOSE_ME` (bool, default `True`): when `False` the current-user
    endpoint is not registered even if a user model is configured.

## Protecting manual routes
Endpoints generated by flarchitect are automatically secured when
API_AUTHENTICATE_METHOD <configuration.html#AUTHENTICATE_METHOD> includes `"jwt"`. If you add your own Flask routes
outside the generated API, decorate them with `jwt_authentication` to enforce
the same protection:
```
from flarchitect.core.architect import jwt_authentication

@app.get("/profile")
@jwt_authentication
def profile() -> dict[str, str]:
    return {"status": "ok"}
```
This decorator reads the `Authorization` header, validates the token and sets
`current_user`. Automatically created endpoints do not need it because global
settings already apply authentication.

## Refresh token storage
By default, flarchitect persists JWT refresh tokens in an SQL table named
`refresh_tokens`. The table contains four columns:
- `token` – the encoded refresh token (primary key)
- `user_pk` – the user's primary key as a string
- `user_lookup` – the configured user lookup value
- `expires_at` – the token's expiry timestamp
The table is created automatically when a refresh token is stored. You can
manage tokens directly using helpers from
`flarchitect.authentication.token_store`:
```
from datetime import datetime, timedelta, timezone
from flarchitect.authentication.token_store import (
    delete_refresh_token,
    get_refresh_token,
    store_refresh_token,
)

expires = datetime.now(timezone.utc) + timedelta(days=1)
store_refresh_token(
    "encoded-token", user_pk="1", user_lookup="alice", expires_at=expires
)

stored = get_refresh_token("encoded-token")
if stored:
    print(stored.user_pk, stored.expires_at)

delete_refresh_token("encoded-token")
```

