From 05a0621954ffba8ec2d0ccd550d4400e3ba7ee99 Mon Sep 17 00:00:00 2001 From: Dhruv Pareek Date: Tue, 21 Apr 2026 21:38:59 -0700 Subject: [PATCH] feat: add PASSKEY branch to auth credential create MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds the PASSKEY branch to `AuthCredentialCreateRequestOneOf`, letting platforms register a WebAuthn passkey as an authentication credential on an Embedded Wallet internal account. **Request shape** - `POST /auth/credentials` body: `{ type: "PASSKEY", accountId, nickname, challenge, attestation }` → 201 `AuthMethod`. **Schemas added** - `PasskeyAttestation` — `{ credentialId, clientDataJson, attestationObject, transports }`, all base64url-encoded fields plus a W3C-enum-validated `transports` array (values: `usb | nfc | ble | internal | hybrid | smart-card`). - `PasskeyCredentialCreateRequestFields` — `{ type: "PASSKEY", nickname, challenge, attestation }` (variant single-value enum on `type`). - `PasskeyCredentialCreateRequest` — `allOf(AuthCredentialCreateRequest, PasskeyCredentialCreateRequestFields)`; wire body is `{ type, accountId, nickname, challenge, attestation }` (accountId inherited from base). **Wire-up** - `AuthCredentialCreateRequestOneOf.yaml` discriminator map extended with `PASSKEY → PasskeyCredentialCreateRequest`. - PASSKEY request example added to `POST /auth/credentials`. - Endpoint description updated to document the PASSKEY path: the platform backend issues the WebAuthn challenge (not Grid), the client completes `navigator.credentials.create()`, and the resulting `attestation` is submitted here. Activation still happens via `/verify`. - `.stainless/stainless.yml` registers the three new schemas under `auth.credentials`. **Design notes** - `transports` values at the Grid API surface are the raw W3C `AuthenticatorTransport` strings as returned by `AuthenticatorAttestationResponse.getTransports()`. Clients pass them through verbatim; provider-specific translation is handled server-side. This keeps the API surface decoupled from the downstream passkey provider. - `nickname` is user-chosen at registration (unlike EMAIL_OTP where it's derived from the email address) and appears on `AuthMethod` responses for credential listings. - Multiple passkey credentials per internal account are allowed (no `PASSKEY_CREDENTIAL_ALREADY_EXISTS`); follows the OAUTH precedent. **Notes** - This PR only wires the create flow; `POST /auth/credentials/{id}/verify` gets its own PASSKEY branch in the next PR in the stack. - Bundled `openapi.yaml` and `mintlify/openapi.yaml` regenerated via `make build`. --- .stainless/stainless.yml | 3 + mintlify/openapi.yaml | 82 ++++++++++++++++++- openapi.yaml | 82 ++++++++++++++++++- .../AuthCredentialCreateRequestOneOf.yaml | 2 + .../schemas/auth/PasskeyAttestation.yaml | 48 +++++++++++ .../auth/PasskeyCredentialCreateRequest.yaml | 4 + .../PasskeyCredentialCreateRequestFields.yaml | 30 +++++++ openapi/paths/auth/auth_credentials.yaml | 21 ++++- 8 files changed, 269 insertions(+), 3 deletions(-) create mode 100644 openapi/components/schemas/auth/PasskeyAttestation.yaml create mode 100644 openapi/components/schemas/auth/PasskeyCredentialCreateRequest.yaml create mode 100644 openapi/components/schemas/auth/PasskeyCredentialCreateRequestFields.yaml diff --git a/.stainless/stainless.yml b/.stainless/stainless.yml index af2154f9..2ad63eab 100644 --- a/.stainless/stainless.yml +++ b/.stainless/stainless.yml @@ -350,6 +350,9 @@ resources: oauth_credential_verify_request_fields: '#/components/schemas/OauthCredentialVerifyRequestFields' oauth_credential_additional_challenge: '#/components/schemas/OauthCredentialAdditionalChallenge' oauth_credential_additional_challenge_fields: '#/components/schemas/OauthCredentialAdditionalChallengeFields' + passkey_attestation: '#/components/schemas/PasskeyAttestation' + passkey_credential_create_request: '#/components/schemas/PasskeyCredentialCreateRequest' + passkey_credential_create_request_fields: '#/components/schemas/PasskeyCredentialCreateRequestFields' exchange_rates: methods: list: diff --git a/mintlify/openapi.yaml b/mintlify/openapi.yaml index 0fd3af70..5e7c43a9 100644 --- a/mintlify/openapi.yaml +++ b/mintlify/openapi.yaml @@ -3745,7 +3745,7 @@ paths: **First credential on an internal account** - If the target internal account does not yet have any authentication credential registered, call this endpoint with the credential details. The response is `201` with the created `AuthMethod`. For `EMAIL_OTP` credentials, this call also triggers a one-time password email to the address on the customer record tied to the internal account; the credential must be activated via `POST /auth/credentials/{id}/verify` before it can sign requests. For `OAUTH` credentials, the supplied `oidcToken` is validated inline against the issuer's `.well-known` OpenID configuration (the token's `iat` must be less than 60 seconds before the request); activation still happens via `POST /auth/credentials/{id}/verify`. + If the target internal account does not yet have any authentication credential registered, call this endpoint with the credential details. The response is `201` with the created `AuthMethod`. For `EMAIL_OTP` credentials, this call also triggers a one-time password email to the address on the customer record tied to the internal account; the credential must be activated via `POST /auth/credentials/{id}/verify` before it can sign requests. For `OAUTH` credentials, the supplied `oidcToken` is validated inline against the issuer's `.well-known` OpenID configuration (the token's `iat` must be less than 60 seconds before the request); activation still happens via `POST /auth/credentials/{id}/verify`. For `PASSKEY` credentials, the client completes a WebAuthn registration (`navigator.credentials.create()`) using a `challenge` issued by the platform backend and submits the resulting `attestation` here; the credential must still be activated via `POST /auth/credentials/{id}/verify` by completing a WebAuthn assertion against a fresh challenge. **Adding an additional credential** @@ -3788,6 +3788,20 @@ paths: type: OAUTH accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 oidcToken: eyJhbGciOiJSUzI1NiIsImtpZCI6ImFiYzEyMyIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLCJzdWIiOiIxMTIyMzM0NDU1IiwiYXVkIjoiMTIzNDU2Ny5hcHBzLmdvb2dsZXVzZXJjb250ZW50LmNvbSIsImVtYWlsIjoidXNlckBleGFtcGxlLmNvbSIsImlhdCI6MTc0NjczNjUwOSwiZXhwIjoxNzQ2NzQwMTA5fQ.signature + passkey: + summary: Register a passkey credential + value: + type: PASSKEY + accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + nickname: Peng's Pixel + challenge: Y2hhbGxlbmdlLWZyb20tcGxhdGZvcm0tYmFja2VuZA + attestation: + credentialId: AQIDBAUGBwgJCgsMDQ4PEA + clientDataJson: eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIjoiLi4uIiwib3JpZ2luIjoiaHR0cHM6Ly9leGFtcGxlLmNvbSJ9 + attestationObject: o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YVikSZYN5YgOjGh0NBcPZHZgW4_krrmihjLHmVzzuoMdl2NdAAAAAA + transports: + - internal + - hybrid responses: '201': description: Authentication credential created successfully @@ -13316,15 +13330,81 @@ components: allOf: - $ref: '#/components/schemas/AuthCredentialCreateRequest' - $ref: '#/components/schemas/OauthCredentialCreateRequestFields' + PasskeyAttestation: + title: Passkey Attestation + type: object + required: + - credentialId + - clientDataJson + - attestationObject + - transports + properties: + credentialId: + type: string + description: Base64url-encoded credential identifier produced by the authenticator at registration time. Typically the base64url of `PublicKeyCredential.rawId`. + example: AQIDBAUGBwgJCgsMDQ4PEA + clientDataJson: + type: string + description: Base64url-encoded CBOR/JSON client data collected by the browser during the WebAuthn `navigator.credentials.create()` call. Corresponds to `AuthenticatorAttestationResponse.clientDataJSON`. + example: eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIjoiLi4uIiwib3JpZ2luIjoiaHR0cHM6Ly9leGFtcGxlLmNvbSJ9 + attestationObject: + type: string + description: Base64url-encoded attestation object produced by the authenticator during registration. Corresponds to `AuthenticatorAttestationResponse.attestationObject`. + example: o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YVikSZYN5YgOjGh0NBcPZHZgW4_krrmihjLHmVzzuoMdl2NdAAAAAA + transports: + type: array + items: + type: string + enum: + - usb + - nfc + - ble + - internal + - hybrid + - smart-card + description: WebAuthn transports as returned by `AuthenticatorAttestationResponse.getTransports()`. Values follow the W3C `AuthenticatorTransport` enum. Pass the raw values through to Grid; provider-specific translation is handled server-side. + example: + - internal + - hybrid + PasskeyCredentialCreateRequestFields: + type: object + required: + - type + - nickname + - challenge + - attestation + properties: + type: + type: string + enum: + - PASSKEY + description: Discriminator value identifying this as a passkey credential. + nickname: + type: string + description: Human-readable identifier for the passkey, chosen by the user at registration time (e.g. "Peng's Pixel", "YubiKey 5C"). Shown back on `AuthMethod` responses and in credential listings. + example: Peng's Pixel + challenge: + type: string + description: Base64url-encoded WebAuthn challenge that the authenticator signed during registration. The challenge is generated and issued by the platform backend (not by Grid), bound to the user and the pending registration, and discarded after this call. Grid uses this value to verify the assertion that the client just completed. + example: Y2hhbGxlbmdlLWZyb20tcGxhdGZvcm0tYmFja2VuZA + attestation: + $ref: '#/components/schemas/PasskeyAttestation' + PasskeyCredentialCreateRequest: + title: Passkey Credential Create Request + allOf: + - $ref: '#/components/schemas/AuthCredentialCreateRequest' + - $ref: '#/components/schemas/PasskeyCredentialCreateRequestFields' AuthCredentialCreateRequestOneOf: oneOf: - $ref: '#/components/schemas/EmailOtpCredentialCreateRequest' - $ref: '#/components/schemas/OauthCredentialCreateRequest' + - $ref: '#/components/schemas/PasskeyCredentialCreateRequest' discriminator: propertyName: type mapping: EMAIL_OTP: '#/components/schemas/EmailOtpCredentialCreateRequest' OAUTH: '#/components/schemas/OauthCredentialCreateRequest' + PASSKEY: '#/components/schemas/PasskeyCredentialCreateRequest' AuthMethod: type: object required: diff --git a/openapi.yaml b/openapi.yaml index 0fd3af70..5e7c43a9 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -3745,7 +3745,7 @@ paths: **First credential on an internal account** - If the target internal account does not yet have any authentication credential registered, call this endpoint with the credential details. The response is `201` with the created `AuthMethod`. For `EMAIL_OTP` credentials, this call also triggers a one-time password email to the address on the customer record tied to the internal account; the credential must be activated via `POST /auth/credentials/{id}/verify` before it can sign requests. For `OAUTH` credentials, the supplied `oidcToken` is validated inline against the issuer's `.well-known` OpenID configuration (the token's `iat` must be less than 60 seconds before the request); activation still happens via `POST /auth/credentials/{id}/verify`. + If the target internal account does not yet have any authentication credential registered, call this endpoint with the credential details. The response is `201` with the created `AuthMethod`. For `EMAIL_OTP` credentials, this call also triggers a one-time password email to the address on the customer record tied to the internal account; the credential must be activated via `POST /auth/credentials/{id}/verify` before it can sign requests. For `OAUTH` credentials, the supplied `oidcToken` is validated inline against the issuer's `.well-known` OpenID configuration (the token's `iat` must be less than 60 seconds before the request); activation still happens via `POST /auth/credentials/{id}/verify`. For `PASSKEY` credentials, the client completes a WebAuthn registration (`navigator.credentials.create()`) using a `challenge` issued by the platform backend and submits the resulting `attestation` here; the credential must still be activated via `POST /auth/credentials/{id}/verify` by completing a WebAuthn assertion against a fresh challenge. **Adding an additional credential** @@ -3788,6 +3788,20 @@ paths: type: OAUTH accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 oidcToken: eyJhbGciOiJSUzI1NiIsImtpZCI6ImFiYzEyMyIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLCJzdWIiOiIxMTIyMzM0NDU1IiwiYXVkIjoiMTIzNDU2Ny5hcHBzLmdvb2dsZXVzZXJjb250ZW50LmNvbSIsImVtYWlsIjoidXNlckBleGFtcGxlLmNvbSIsImlhdCI6MTc0NjczNjUwOSwiZXhwIjoxNzQ2NzQwMTA5fQ.signature + passkey: + summary: Register a passkey credential + value: + type: PASSKEY + accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + nickname: Peng's Pixel + challenge: Y2hhbGxlbmdlLWZyb20tcGxhdGZvcm0tYmFja2VuZA + attestation: + credentialId: AQIDBAUGBwgJCgsMDQ4PEA + clientDataJson: eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIjoiLi4uIiwib3JpZ2luIjoiaHR0cHM6Ly9leGFtcGxlLmNvbSJ9 + attestationObject: o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YVikSZYN5YgOjGh0NBcPZHZgW4_krrmihjLHmVzzuoMdl2NdAAAAAA + transports: + - internal + - hybrid responses: '201': description: Authentication credential created successfully @@ -13316,15 +13330,81 @@ components: allOf: - $ref: '#/components/schemas/AuthCredentialCreateRequest' - $ref: '#/components/schemas/OauthCredentialCreateRequestFields' + PasskeyAttestation: + title: Passkey Attestation + type: object + required: + - credentialId + - clientDataJson + - attestationObject + - transports + properties: + credentialId: + type: string + description: Base64url-encoded credential identifier produced by the authenticator at registration time. Typically the base64url of `PublicKeyCredential.rawId`. + example: AQIDBAUGBwgJCgsMDQ4PEA + clientDataJson: + type: string + description: Base64url-encoded CBOR/JSON client data collected by the browser during the WebAuthn `navigator.credentials.create()` call. Corresponds to `AuthenticatorAttestationResponse.clientDataJSON`. + example: eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIjoiLi4uIiwib3JpZ2luIjoiaHR0cHM6Ly9leGFtcGxlLmNvbSJ9 + attestationObject: + type: string + description: Base64url-encoded attestation object produced by the authenticator during registration. Corresponds to `AuthenticatorAttestationResponse.attestationObject`. + example: o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YVikSZYN5YgOjGh0NBcPZHZgW4_krrmihjLHmVzzuoMdl2NdAAAAAA + transports: + type: array + items: + type: string + enum: + - usb + - nfc + - ble + - internal + - hybrid + - smart-card + description: WebAuthn transports as returned by `AuthenticatorAttestationResponse.getTransports()`. Values follow the W3C `AuthenticatorTransport` enum. Pass the raw values through to Grid; provider-specific translation is handled server-side. + example: + - internal + - hybrid + PasskeyCredentialCreateRequestFields: + type: object + required: + - type + - nickname + - challenge + - attestation + properties: + type: + type: string + enum: + - PASSKEY + description: Discriminator value identifying this as a passkey credential. + nickname: + type: string + description: Human-readable identifier for the passkey, chosen by the user at registration time (e.g. "Peng's Pixel", "YubiKey 5C"). Shown back on `AuthMethod` responses and in credential listings. + example: Peng's Pixel + challenge: + type: string + description: Base64url-encoded WebAuthn challenge that the authenticator signed during registration. The challenge is generated and issued by the platform backend (not by Grid), bound to the user and the pending registration, and discarded after this call. Grid uses this value to verify the assertion that the client just completed. + example: Y2hhbGxlbmdlLWZyb20tcGxhdGZvcm0tYmFja2VuZA + attestation: + $ref: '#/components/schemas/PasskeyAttestation' + PasskeyCredentialCreateRequest: + title: Passkey Credential Create Request + allOf: + - $ref: '#/components/schemas/AuthCredentialCreateRequest' + - $ref: '#/components/schemas/PasskeyCredentialCreateRequestFields' AuthCredentialCreateRequestOneOf: oneOf: - $ref: '#/components/schemas/EmailOtpCredentialCreateRequest' - $ref: '#/components/schemas/OauthCredentialCreateRequest' + - $ref: '#/components/schemas/PasskeyCredentialCreateRequest' discriminator: propertyName: type mapping: EMAIL_OTP: '#/components/schemas/EmailOtpCredentialCreateRequest' OAUTH: '#/components/schemas/OauthCredentialCreateRequest' + PASSKEY: '#/components/schemas/PasskeyCredentialCreateRequest' AuthMethod: type: object required: diff --git a/openapi/components/schemas/auth/AuthCredentialCreateRequestOneOf.yaml b/openapi/components/schemas/auth/AuthCredentialCreateRequestOneOf.yaml index cfa9ea30..814d26ad 100644 --- a/openapi/components/schemas/auth/AuthCredentialCreateRequestOneOf.yaml +++ b/openapi/components/schemas/auth/AuthCredentialCreateRequestOneOf.yaml @@ -1,8 +1,10 @@ oneOf: - $ref: ./EmailOtpCredentialCreateRequest.yaml - $ref: ./OauthCredentialCreateRequest.yaml + - $ref: ./PasskeyCredentialCreateRequest.yaml discriminator: propertyName: type mapping: EMAIL_OTP: ./EmailOtpCredentialCreateRequest.yaml OAUTH: ./OauthCredentialCreateRequest.yaml + PASSKEY: ./PasskeyCredentialCreateRequest.yaml diff --git a/openapi/components/schemas/auth/PasskeyAttestation.yaml b/openapi/components/schemas/auth/PasskeyAttestation.yaml new file mode 100644 index 00000000..30ddab16 --- /dev/null +++ b/openapi/components/schemas/auth/PasskeyAttestation.yaml @@ -0,0 +1,48 @@ +title: Passkey Attestation +type: object +required: + - credentialId + - clientDataJson + - attestationObject + - transports +properties: + credentialId: + type: string + description: >- + Base64url-encoded credential identifier produced by the authenticator + at registration time. Typically the base64url of + `PublicKeyCredential.rawId`. + example: AQIDBAUGBwgJCgsMDQ4PEA + clientDataJson: + type: string + description: >- + Base64url-encoded CBOR/JSON client data collected by the browser + during the WebAuthn `navigator.credentials.create()` call. + Corresponds to `AuthenticatorAttestationResponse.clientDataJSON`. + example: eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIjoiLi4uIiwib3JpZ2luIjoiaHR0cHM6Ly9leGFtcGxlLmNvbSJ9 + attestationObject: + type: string + description: >- + Base64url-encoded attestation object produced by the authenticator + during registration. Corresponds to + `AuthenticatorAttestationResponse.attestationObject`. + example: o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YVikSZYN5YgOjGh0NBcPZHZgW4_krrmihjLHmVzzuoMdl2NdAAAAAA + transports: + type: array + items: + type: string + enum: + - usb + - nfc + - ble + - internal + - hybrid + - smart-card + description: >- + WebAuthn transports as returned by + `AuthenticatorAttestationResponse.getTransports()`. Values follow + the W3C `AuthenticatorTransport` enum. Pass the raw values through + to Grid; provider-specific translation is handled server-side. + example: + - internal + - hybrid diff --git a/openapi/components/schemas/auth/PasskeyCredentialCreateRequest.yaml b/openapi/components/schemas/auth/PasskeyCredentialCreateRequest.yaml new file mode 100644 index 00000000..6f862511 --- /dev/null +++ b/openapi/components/schemas/auth/PasskeyCredentialCreateRequest.yaml @@ -0,0 +1,4 @@ +title: Passkey Credential Create Request +allOf: + - $ref: ./AuthCredentialCreateRequest.yaml + - $ref: ./PasskeyCredentialCreateRequestFields.yaml diff --git a/openapi/components/schemas/auth/PasskeyCredentialCreateRequestFields.yaml b/openapi/components/schemas/auth/PasskeyCredentialCreateRequestFields.yaml new file mode 100644 index 00000000..d34ee00d --- /dev/null +++ b/openapi/components/schemas/auth/PasskeyCredentialCreateRequestFields.yaml @@ -0,0 +1,30 @@ +type: object +required: + - type + - nickname + - challenge + - attestation +properties: + type: + type: string + enum: + - PASSKEY + description: Discriminator value identifying this as a passkey credential. + nickname: + type: string + description: >- + Human-readable identifier for the passkey, chosen by the user at + registration time (e.g. "Peng's Pixel", "YubiKey 5C"). Shown back + on `AuthMethod` responses and in credential listings. + example: Peng's Pixel + challenge: + type: string + description: >- + Base64url-encoded WebAuthn challenge that the authenticator signed + during registration. The challenge is generated and issued by the + platform backend (not by Grid), bound to the user and the pending + registration, and discarded after this call. Grid uses this value + to verify the assertion that the client just completed. + example: Y2hhbGxlbmdlLWZyb20tcGxhdGZvcm0tYmFja2VuZA + attestation: + $ref: ./PasskeyAttestation.yaml diff --git a/openapi/paths/auth/auth_credentials.yaml b/openapi/paths/auth/auth_credentials.yaml index 1bbac911..0371d3fe 100644 --- a/openapi/paths/auth/auth_credentials.yaml +++ b/openapi/paths/auth/auth_credentials.yaml @@ -17,7 +17,12 @@ post: `oidcToken` is validated inline against the issuer's `.well-known` OpenID configuration (the token's `iat` must be less than 60 seconds before the request); activation still happens via - `POST /auth/credentials/{id}/verify`. + `POST /auth/credentials/{id}/verify`. For `PASSKEY` credentials, the + client completes a WebAuthn registration (`navigator.credentials.create()`) + using a `challenge` issued by the platform backend and submits the + resulting `attestation` here; the credential must still be activated + via `POST /auth/credentials/{id}/verify` by completing a WebAuthn + assertion against a fresh challenge. **Adding an additional credential** @@ -84,6 +89,20 @@ post: type: OAUTH accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 oidcToken: eyJhbGciOiJSUzI1NiIsImtpZCI6ImFiYzEyMyIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLCJzdWIiOiIxMTIyMzM0NDU1IiwiYXVkIjoiMTIzNDU2Ny5hcHBzLmdvb2dsZXVzZXJjb250ZW50LmNvbSIsImVtYWlsIjoidXNlckBleGFtcGxlLmNvbSIsImlhdCI6MTc0NjczNjUwOSwiZXhwIjoxNzQ2NzQwMTA5fQ.signature + passkey: + summary: Register a passkey credential + value: + type: PASSKEY + accountId: InternalAccount:019542f5-b3e7-1d02-0000-000000000002 + nickname: Peng's Pixel + challenge: Y2hhbGxlbmdlLWZyb20tcGxhdGZvcm0tYmFja2VuZA + attestation: + credentialId: AQIDBAUGBwgJCgsMDQ4PEA + clientDataJson: eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIjoiLi4uIiwib3JpZ2luIjoiaHR0cHM6Ly9leGFtcGxlLmNvbSJ9 + attestationObject: o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YVikSZYN5YgOjGh0NBcPZHZgW4_krrmihjLHmVzzuoMdl2NdAAAAAA + transports: + - internal + - hybrid responses: '201': description: Authentication credential created successfully