# `Jwk`

A public key that a token is checked against, as an identity provider publishes it.

Use it with `crypto.verify_jwt`. To check other signatures with a raw Ed25519 key, use [`PublicKey`](/docs/types/type-public-key.md).

## Shape

| Field | Type | Required? |
|---|---|---|
| `kty` | `Text` | Yes |
| `crv` | `Text` | No |
| `x` | `Text` | No |
| `y` | `Text` | No |
| `n` | `Text` | No |
| `e` | `Text` | No |
| `kid` | `Text` | No |
| `alg` | `Text` | No |
| `use` | `Text` | No |
| `key_ops` | `Set<Text>` | No |
| `x5c` | `List<Text>` | No |

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

Its parts keep these rules, and a value that breaks one fails with the rule's name:

- `parts_of_its_kty`: an `OKP` key has `crv` `Ed25519` and `x`, an `RSA` key `n` and `e`, and an `EC` key `crv` `P-256`, `x` and `y`, and none another's
- `algorithm_of_its_kty`: alg is EdDSA for an `OKP` key, RS256 for an `RSA` key and ES256 for an `EC` key
- `a_usable_key`: an `RSA` key's modulus is 2,048 to 4,096 bits and odd, its exponent odd and from 3 to 2^32 - 1; an `EC` key's point is on P-256

## Example

```jadpo
Jwk {
    kty: "OKP"
    crv: "Ed25519"
    x: "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo"
    y: none
    n: none
    e: none
    kid: none
    alg: none
    use: none
    key_ops: none
    x5c: none
}
```

## Good to know

- `kty` decides which of the other parts it needs: `OKP`, `RSA` or `EC`.
- `==` 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
{"kty":"OKP","crv":"Ed25519","x":"11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo","kid":"2026-10","alg":"EdDSA","use":"sig"}
{"kty":"OKP","crv":"Ed25519","x":"PUAXw-hDiVqStwqnTRt-vJyYLM8uxJaMwM1V8Sr0Zgw"}
{"kty":"OKP","crv":"Ed25519","x":"PUAXw-hDiVqStwqnTRt-vJyYLM8uxJaMwM1V8Sr0Zgw","key_ops":["verify"]}
```

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>

`Jwk` 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 11 columns, each named for the field, `_` and its suffix. This is `{"kty":"EC","crv":"P-256","x":"MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4","y":"4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM","kid":"1","use":"sig"}`:

| Column | Type | Holds |
|---|---|---|
| `<field>_kty` | `text` | `'EC'` |
| `<field>_crv` | `text` | `'P-256'` |
| `<field>_x` | `text` | `'MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4'` |
| `<field>_y` | `text` | `'4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM'` |
| `<field>_n` | `text` | `NULL::text` |
| `<field>_e` | `text` | `NULL::text` |
| `<field>_kid` | `text` | `'1'` |
| `<field>_alg` | `text` | `NULL::text` |
| `<field>_use` | `text` | `'sig'` |
| `<field>_key_ops` | `text[]` | `NULL::text[]` |
| `<field>_x5c` | `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 |
|---|---|
| `{"kty":"OKP","crv":"Ed25519","x":"11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo","kid":"2026-10","alg":"EdDSA","use":"sig"}` | `2026-10` |
| `{"kty":"OKP","crv":"Ed25519","x":"PUAXw-hDiVqStwqnTRt-vJyYLM8uxJaMwM1V8Sr0Zgw"}` | `PUAXw-hDiVqStwqnTRt-vJyYLM8uxJaMwM1V8Sr0Zgw` |
| `{"kty":"OKP","crv":"Ed25519","x":"PUAXw-hDiVqStwqnTRt-vJyYLM8uxJaMwM1V8Sr0Zgw","key_ops":["verify"]}` | `PUAXw-hDiVqStwqnTRt-vJyYLM8uxJaMwM1V8Sr0Zgw` |
| `{"kty":"EC","crv":"P-256","x":"MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4","y":"4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM","kid":"1","use":"sig"}` | `1` |
| `{"kty":"RSA","n":"0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw","e":"AQAB","kid":"2011-04-29","alg":"RS256"}` | `2011-04-29` |
| `{"kty":"EC","crv":"P-256","x":"f83OJ3D2xF1Bg8vub9tLe1gHMzV76e8Tus9uPHvRVEU","y":"x_FEzRu9m36HLN_tue659LNpXW6pCyStikYjKIWI5a0","alg":"ES256"}` | `f83OJ3D2xF1Bg8vub9tLe1gHMzV76e8Tus9uPHvRVEU` |
| `{"kty":"RSA","n":"ofgWCuLjybRlzo0tZWJjNiuSfb4p4fAkd_wWJcyQoTbji9k0l8W26mPddxHmfHQp-Vaw-4qPCJrcS2mJPMEzP1Pt0Bm4d4QlL-yRT-SFd2lZS-pCgNMsD1W_YpRPEwOWvG6b32690r2jZ47soMZo9wGzjb_7OMg0LOL-bSf63kpaSHSXndS5z5rexMdbBYUsLA9e-KXBdQOS-UTo7WTBEMa2R2CapHg665xsmtdVMTBQY4uDZlxvb3qCo5ZwKh9kG4LT6_I5IhlJH7aGhyxXFvUK-DWNmoudF8NAco9_h9iaGNj8q2ethFkMLs91kzk2PAcDTW9gb54h4FRWyuXpoQ","e":"AQAB"}` | `ofgWCuLjybRlzo0tZWJjNiuSfb4p4fAkd_wWJcyQoTbji9k0l8W26mPddxHmfHQp-Vaw-4qPCJrcS2mJPMEzP1Pt0Bm4d4QlL-yRT-SFd2lZS-pCgNMsD1W_YpRPEwOWvG6b32690r2jZ47soMZo9wGzjb_7OMg0LOL-bSf63kpaSHSXndS5z5rexMdbBYUsLA9e-KXBdQOS-UTo7WTBEMa2R2CapHg665xsmtdVMTBQY4uDZlxvb3qCo5ZwKh9kG4LT6_I5IhlJH7aGhyxXFvUK-DWNmoudF8NAco9_h9iaGNj8q2ethFkMLs91kzk2PAcDTW9gb54h4FRWyuXpoQ` |

### 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>
