# `JwtClaims`

What a checked token says: who issued it, who it is about and when it expires.

Use what `crypto.verify_jwt` gives you, once it has checked the token. For a token itself, use [`Jwt`](/docs/types/type-jwt.md). For a signed-in person through OpenID Connect, use [`OidcClaims`](/docs/types/type-oidc-claims.md).

## Shape

| Field | Type | Required? |
|---|---|---|
| `iss` | `Text` | No |
| `sub` | `Text` | No |
| `aud` | `List<Text>` | Yes |
| `exp` | [`Instant`](/docs/types/type-instant.md) | Yes |
| `nbf` | [`Instant`](/docs/types/type-instant.md) | No |
| `iat` | [`Instant`](/docs/types/type-instant.md) | No |
| `jti` | `Text` | No |

Write every field when you build one, and give a field that isn't required as `none`.

## Example

```jadpo
JwtClaims {
    iss: none
    sub: "member-42"
    aud: []
    exp: "2026-10-18T10:00:00Z"
    nbf: none
    iat: none
    jti: none
}
```

This operation gives one: `crypto.verify_jwt`.

## Good to know

- `exp` is always there, and `aud` is always a list.
- `==` tells you whether two are the same. `<` and `list.sort_by` don't take it, so sort by one of its parts instead.

## In JSON

An object of its parts, in a request, a response or `json.write`:

```json
{"iss":"https://id.example.com","sub":"member-42","aud":["clinic"],"exp":"2026-10-18T10:00:00Z","nbf":"2026-10-18T09:00:00Z","iat":"2026-10-18T09:00:00Z","jti":"a1b2c3"}
{"iss":"https://id.example.com","aud":[],"exp":"2026-10-18T10:00:00Z"}
{"aud":[],"exp":"2026-10-18T10:00:00Z"}
```

When you read one, a field that isn't required may be left out, or sent as `null`.

## Details

<details>
<summary>Text form, storage, how it is shown, and operations</summary>

`JwtClaims` is in the group Security, and has been in Jadpo since 0.1.0.

### Text form

It has no single text form, so `text.from` and `text.parse` don't take it.

### How it is stored

It is stored in 7 columns, each named for the field, `_` and its suffix. This is `{"iss":"https://id.example.com","aud":[],"exp":"2026-10-18T10:00:00Z"}`:

| Column | Type | Holds |
|---|---|---|
| `<field>_iss` | `text` | `'https://id.example.com'` |
| `<field>_sub` | `text` | `NULL::text` |
| `<field>_aud` | `text[]` | `'{}'::text[]` |
| `<field>_exp` | `timestamptz(3)` | `'2026-10-18 10:00:00.000000+00'::timestamptz` |
| `<field>_nbf` | `timestamptz(3)` | `NULL::timestamptz` |
| `<field>_iat` | `timestamptz(3)` | `NULL::timestamptz` |
| `<field>_jti` | `text` | `NULL::text` |

### How it is shown to people

`text.format(value, locale:)` shows it to people in their locale.

With `locale: Locale.en_gb`:

| Value | Shown |
|---|---|
| `{"iss":"https://id.example.com","sub":"member-42","aud":["clinic"],"exp":"2026-10-18T10:00:00Z","nbf":"2026-10-18T09:00:00Z","iat":"2026-10-18T09:00:00Z","jti":"a1b2c3"}` | `member-42` |
| `{"iss":"https://id.example.com","aud":[],"exp":"2026-10-18T10:00:00Z"}` | `https://id.example.com` |
| `{"aud":[],"exp":"2026-10-18T10:00:00Z"}` | `2026-10-18T10:00:00Z` |

### Operations

- `crypto.verify_jwt(token: Jwt, keys: List<Jwk>, issuer: Text?, audience: Text?, as: Type?) -> JwtClaims`: The registered claims of `token` once it holds: signed by one of `keys` with that key's own algorithm (Ed25519, RS256 for an RSA key, ES256 for a P-256 key), never the one the token names, so `none` and every other algorithm fail `invalid_signature`; not expired, not used before its `nbf` and not issued later than now, against the clock with 30 seconds' leeway (`expired`, `not_yet_valid`); issued by `issuer` when one is given (`wrong_issuer`); and for `audience`, or for no audience when none is given (`wrong_audience`). A token that isn't one, or has no `exp`, is `malformed`. With `as:`, an object type of the application's, it gives instead the token's claims the type names, read as `json.read` reads it, a registered one as `JwtClaims` holds it, and fails `mismatch` when they aren't the type. It reads the clock, so it is called only in actions.

It also takes `text.format`, `json.read`, `json.write` and `==`. [How every standard type behaves](/docs/every-standard-type-meets-one-contract.md) says how they work.

</details>
