From 9506f096c7bfde9d48f996dfc08e96217f48def6 Mon Sep 17 00:00:00 2001 From: Dhruv Pareek Date: Fri, 22 May 2026 14:44:59 -0700 Subject: [PATCH] docs: document realistic sandbox passkeys --- mintlify/openapi.yaml | 1 + .../snippets/sandbox-global-account-magic.mdx | 24 ++++++++++++------- openapi.yaml | 1 + .../schemas/auth/PasskeyAssertion.yaml | 5 ++++ 4 files changed, 23 insertions(+), 8 deletions(-) diff --git a/mintlify/openapi.yaml b/mintlify/openapi.yaml index 5bd69dc8..cb10e6ed 100644 --- a/mintlify/openapi.yaml +++ b/mintlify/openapi.yaml @@ -16533,6 +16533,7 @@ components: PasskeyAssertion: title: Passkey Assertion type: object + description: WebAuthn assertion returned by `navigator.credentials.get()`. In sandbox, Grid validates the assertion against the registered passkey credential so the client-side flow can match production. In production, Turnkey validates the WebAuthn assertion. required: - credentialId - clientDataJson diff --git a/mintlify/snippets/sandbox-global-account-magic.mdx b/mintlify/snippets/sandbox-global-account-magic.mdx index b82368db..a988cd3d 100644 --- a/mintlify/snippets/sandbox-global-account-magic.mdx +++ b/mintlify/snippets/sandbox-global-account-magic.mdx @@ -1,6 +1,6 @@ -The Grid sandbox accepts a small set of test helpers for Global Account flows, so you can exercise the full request shape without real OTP delivery, WebAuthn ceremony, or wallet signatures. Email OTP uses a sandbox test inbox. Passkey and wallet signatures use fixed sandbox-only values. OAuth accepts OIDC ID tokens from supported providers; for isolated sandbox tests, you can also pass a JWT-shaped test token. Sandbox skips real IdP signature verification, but still validates token claims, freshness, credential identity, and verify-time nonce binding. +The Grid sandbox lets you exercise Global Account auth flows without moving real money. Email OTP uses a sandbox test inbox. Passkey auth can use the same browser WebAuthn ceremony as production. Wallet signatures use fixed sandbox-only values. OAuth accepts OIDC ID tokens from supported providers; for isolated sandbox tests, you can also pass a JWT-shaped test token. Sandbox skips real IdP signature verification, but still validates token claims, freshness, credential identity, and verify-time nonce binding. -A wrong magic value or sandbox OIDC authentication failure returns `401 UNAUTHORIZED` with a `reason` field that names the specific check that failed. A malformed OIDC JWT can return `400 INVALID_INPUT` before authentication starts. +Sandbox-only compatibility values are still available for some flows, but they do not exercise the production-shaped client implementation. Authentication failures return `401 UNAUTHORIZED` with a `reason` field that names the specific check that failed. A malformed OIDC JWT can return `400 INVALID_INPUT` before authentication starts. ### Email OTP code @@ -24,11 +24,17 @@ curl -X POST "$GRID_BASE_URL/auth/credentials/AuthMethod:abc123/verify" \ The sandbox validates the code against the pending OTP state. A wrong, expired, consumed, or locked-out code returns `401 UNAUTHORIZED`. -### Passkey assertion signature +### Passkey WebAuthn ceremony -Pass `sandbox-valid-passkey-signature` as `assertion.signature` on `POST /auth/credentials/{id}/verify` when the credential type is `PASSKEY`. The sandbox accepts the rest of the assertion as-is and skips the WebAuthn signature check. +For new sandbox integrations, use the same WebAuthn calls you plan to use in production. -Passkey reauthentication is a two-step `/challenge` → `/verify` flow. The `clientPublicKey` is sent on `/challenge` (so Grid can seal the session signing key to your device) — the magic value bypasses the credential check, not the HPKE plumbing, so the public key is still required. +1. Generate your own WebAuthn registration challenge and call `navigator.credentials.create()`. +2. Register the passkey with `POST /auth/credentials`, passing the challenge and attestation returned by the browser. +3. Reauthenticate with `POST /auth/credentials/{id}/challenge`, passing the P-256 `clientPublicKey` that Grid should seal the session signing key to. +4. Pass the returned `challenge` into `navigator.credentials.get()` using the returned `credentialId` in `allowCredentials`. +5. Verify with `POST /auth/credentials/{id}/verify`, passing the browser assertion and echoing `Request-Id` from the challenge response. + +The sandbox validates the registered credential ID, WebAuthn challenge, origin/RP binding, user-presence bit, assertion signature, and signature counter. A successful verify response includes `encryptedSessionSigningKey`, sealed to the `clientPublicKey`, just like production. ```bash # 1. /challenge with clientPublicKey @@ -39,7 +45,7 @@ curl -X POST https://api.lightspark.com/grid/2025-10-13/auth/credentials/AuthMet "clientPublicKey": "04f45f2a..." }' -# 2. /verify with the magic signature, no clientPublicKey +# 2. /verify with the browser assertion returned by navigator.credentials.get() curl -X POST https://api.lightspark.com/grid/2025-10-13/auth/credentials/AuthMethod:abc123/verify \ -u "$GRID_CLIENT_ID:$GRID_CLIENT_SECRET" \ -H "Content-Type: application/json" \ @@ -50,12 +56,14 @@ curl -X POST https://api.lightspark.com/grid/2025-10-13/auth/credentials/AuthMet "credentialId": "...", "clientDataJson": "...", "authenticatorData": "...", - "signature": "sandbox-valid-passkey-signature" + "signature": "..." } }' ``` -Any other signature returns `401 UNAUTHORIZED` with `reason: "Invalid passkey signature"`. + + The legacy sandbox-only assertion signature `sandbox-valid-passkey-signature` is still accepted for compatibility, but it skips WebAuthn verification and should not be used for production-shaped sandbox tests. + ### OAuth (OIDC) token diff --git a/openapi.yaml b/openapi.yaml index 5bd69dc8..cb10e6ed 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -16533,6 +16533,7 @@ components: PasskeyAssertion: title: Passkey Assertion type: object + description: WebAuthn assertion returned by `navigator.credentials.get()`. In sandbox, Grid validates the assertion against the registered passkey credential so the client-side flow can match production. In production, Turnkey validates the WebAuthn assertion. required: - credentialId - clientDataJson diff --git a/openapi/components/schemas/auth/PasskeyAssertion.yaml b/openapi/components/schemas/auth/PasskeyAssertion.yaml index c92062b6..bf50e06d 100644 --- a/openapi/components/schemas/auth/PasskeyAssertion.yaml +++ b/openapi/components/schemas/auth/PasskeyAssertion.yaml @@ -1,5 +1,10 @@ title: Passkey Assertion type: object +description: >- + WebAuthn assertion returned by `navigator.credentials.get()`. In sandbox, + Grid validates the assertion against the registered passkey credential so the + client-side flow can match production. In production, Turnkey validates the + WebAuthn assertion. required: - credentialId - clientDataJson