# `WebAuthnCredential`

A passkey a person registered.

Use it if you handle passkeys yourself. Jadpo's own sign-in stores these for you, so most apps never touch one.

## Shape

| Field | Type | Required? |
|---|---|---|
| `credential_id` | `Bytes` | Yes |
| `public_key` | `Bytes` | Yes |
| `sign_count` | `Int` | Yes |
| `transports` | `List<Text>` | Yes |
| `backup_eligible` | `Bool` | Yes |
| `backed_up` | `Bool` | Yes |
| `name` | `Text` | Yes |
| `registered_at` | [`Instant`](/docs/types/type-instant.md) | Yes |
| `last_used_at` | [`Instant`](/docs/types/type-instant.md) | No |

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

## Example

```jadpo
type NotAPasskey = Failure {
    kind: FailureKind.invalid_value
    message: "That isn't a passkey."
}

var example: WebAuthnCredential = attempt json.read(
    "{\"credential_id\":\"AAECAwQFBgcICQoLDA0ODw==\",\"public_key\":\"pQECAyYgASFYIGXtpaEld8K66ClDf+M4cBoQqqN14btbXeEI3kOcCFUdIlggHlLtdXARY/f55A3fnzQbPcm6hgr34Mp8p+nuzQCE0Zw=\",\"sign_count\":0,\"transports\":[\"internal\",\"hybrid\"],\"backup_eligible\":true,\"backed_up\":true,\"name\":\"Work phone\",\"registered_at\":\"2026-10-18T09:00:00Z\",\"last_used_at\":\"2026-10-18T09:05:00Z\"}",
    as: WebAuthnCredential
) {
    invalid: NotAPasskey
}
assert example == example
```

## Good to know

- `==` 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
{"credential_id":"AAECAwQFBgcICQoLDA0ODw==","public_key":"pQECAyYgASFYIGXtpaEld8K66ClDf+M4cBoQqqN14btbXeEI3kOcCFUdIlggHlLtdXARY/f55A3fnzQbPcm6hgr34Mp8p+nuzQCE0Zw=","sign_count":0,"transports":["internal","hybrid"],"backup_eligible":true,"backed_up":true,"name":"Work phone","registered_at":"2026-10-18T09:00:00Z","last_used_at":"2026-10-18T09:05:00Z"}
{"credential_id":"8dA=","public_key":"pAEBAycgBiFYINdamAGCsQq31Uv+08lkBzoO4XLz2qYjJa8CGmj3B1Ea","sign_count":42,"transports":["usb","nfc"],"backup_eligible":false,"backed_up":false,"name":"Security key","registered_at":"2026-10-18T09: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>

`WebAuthnCredential` 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 9 columns, each named for the field, `_` and its suffix. This is `{"credential_id":"8dA=","public_key":"pAEBAycgBiFYINdamAGCsQq31Uv+08lkBzoO4XLz2qYjJa8CGmj3B1Ea","sign_count":42,"transports":["usb","nfc"],"backup_eligible":false,"backed_up":false,"name":"Security key","registered_at":"2026-10-18T09:00:00Z"}`:

| Column | Type | Holds |
|---|---|---|
| `<field>_credential_id` | `bytea` | `E'\\xf1d0'::bytea` |
| `<field>_public_key` | `bytea` | `E'\\xa4010103272006215820d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a'::bytea` |
| `<field>_sign_count` | `bigint` | `42` |
| `<field>_transports` | `text[]` | `ARRAY['usb', 'nfc']::text[]` |
| `<field>_backup_eligible` | `boolean` | `FALSE` |
| `<field>_backed_up` | `boolean` | `FALSE` |
| `<field>_name` | `text` | `'Security key'` |
| `<field>_registered_at` | `timestamptz(3)` | `'2026-10-18 09:00:00.000000+00'::timestamptz` |
| `<field>_last_used_at` | `timestamptz(3)` | `NULL::timestamptz` |

### How it is shown to people

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

With `locale: Locale.en_gb`:

| Value | Shown |
|---|---|
| `{"credential_id":"AAECAwQFBgcICQoLDA0ODw==","public_key":"pQECAyYgASFYIGXtpaEld8K66ClDf+M4cBoQqqN14btbXeEI3kOcCFUdIlggHlLtdXARY/f55A3fnzQbPcm6hgr34Mp8p+nuzQCE0Zw=","sign_count":0,"transports":["internal","hybrid"],"backup_eligible":true,"backed_up":true,"name":"Work phone","registered_at":"2026-10-18T09:00:00Z","last_used_at":"2026-10-18T09:05:00Z"}` | `Work phone` |
| `{"credential_id":"8dA=","public_key":"pAEBAycgBiFYINdamAGCsQq31Uv+08lkBzoO4XLz2qYjJa8CGmj3B1Ea","sign_count":42,"transports":["usb","nfc"],"backup_eligible":false,"backed_up":false,"name":"Security key","registered_at":"2026-10-18T09:00:00Z"}` | `Security key` |

### Operations

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>
