Skip to content

docs(auth): document client-held session key as the primary Embedded Wallet flow - #811

Closed
carsonp6 wants to merge 1 commit into
mainfrom
grid-embedded-wallet-client-held-session-key-docs
Closed

docs(auth): document client-held session key as the primary Embedded Wallet flow#811
carsonp6 wants to merge 1 commit into
mainfrom
grid-embedded-wallet-client-held-session-key-docs

Conversation

@carsonp6

Copy link
Copy Markdown
Contributor

Summary

Documents the Embedded Wallet login-family migration to a client-held session signing key. The client-held model already exists in the spec for EMAIL_OTP / SMS_OTP (the client generates the key pair and keeps the private key; the server returns no session key). This PR extends that documented pattern to OAUTH and PASSKEY and reframes the server-sealed encryptedSessionSigningKey as the legacy path being retired.

The encoding of clientPublicKey selects the flow:

  • Compressed SEC1 (02/03 prefix, 66 hex chars) → recommended client-held-key flow. The client retains the private key as the session signing key; the response omits encryptedSessionSigningKey.
  • Uncompressed SEC1 (04 prefix, 130 hex chars) → deprecated legacy flow. Grid seals the session signing key to the client public key and returns it as encryptedSessionSigningKey.

Additive and non-breaking — no info.version bump.

Changes

Deprecate the server-sealed key (components/schemas/auth/AuthSession.yaml)

  • encryptedSessionSigningKey: deprecated: true; description rewritten to legacy-only.
  • Schema + expiresAt descriptions reframed around the client-held key.

Reframe prose to make client-held the primary flow

  • paths/auth/auth_credentials_{id}_verify.yaml — session-key delivery paragraph.
  • paths/auth/auth_sessions_{id}_refresh.yaml — two-step flow description.
  • paths/auth/auth_credentials.yaml — create-credential (existing-credential signing key).
  • clientPublicKey field descriptions in OauthCredentialVerifyRequestFields.yaml, AuthCredentialChallengeRequest.yaml, AuthSessionRefreshRequest.yaml.
  • Representative clientPublicKey examples switched to the compressed encoding; refresh 201 example no longer shows encryptedSessionSigningKey.

Accept compressed keys (validation, non-breaking)

  • AuthCredentialChallengeRequest.yaml and AuthSessionRefreshRequest.yaml: clientPublicKey pattern ^04[0-9a-fA-F]{128}$^(0[23][0-9a-fA-F]{64}|04[0-9a-fA-F]{128})$; challenge minLength 130 → 66. (OauthCredentialVerifyRequestFields.yaml had no pattern and already accepted both.)

Narrative doc (mintlify/snippets/global-accounts/client-keys.mdx, the "Client keys & signing" guide)

  • New "Client-held session key" section describing the recommended flow, mirroring the existing OTP client-held statement.
  • Intro/table/step 1 reframed for both flows; decrypt steps 2–3 marked legacy.

Validation

  • make lint (redocly bundle + lint + spectral, Node 22): pass — "Woohoo! Your API description is valid." 0 spectral errors.
  • Root + mintlify/openapi.yaml bundles rebuilt via npm run build:openapi and committed (identical).
  • oasdiff breaking --fail-on ERR vs main: 0 errors (2 request-property-pattern-changed warnings for the relaxed patterns; a relaxation is a superset, so no breaking-change label).

…Wallet flow

Extend the existing client-held session key model (already used by
EMAIL_OTP/SMS_OTP) to OAUTH and PASSKEY session issuance, and reframe the
server-sealed key as the legacy path being retired.

- Deprecate `AuthSession.encryptedSessionSigningKey` (`deprecated: true`) and
  rewrite its description: returned only for the legacy flow (uncompressed
  clientPublicKey); absent in the client-held-key flow (compressed
  clientPublicKey), where the client generates and retains the session
  signing key.
- Reframe the verify / refresh / create-credential endpoint descriptions and
  the `clientPublicKey` field descriptions so the client-held key is the
  recommended flow. Compressed SEC1 = client-held; uncompressed SEC1 = legacy
  seal.
- Accept compressed SEC1 keys: relax the clientPublicKey `pattern` on the
  challenge and refresh request schemas to `^(0[23]…{64}|04…{128})$` (and
  minLength 130 -> 66). Non-breaking (superset); oasdiff reports 0 errors.
- Add a "Client-held session key" section to the Client keys & signing guide
  and mark the decrypt steps as legacy.

Additive and non-breaking; no version bump.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@vercel

vercel Bot commented Aug 10, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

2 Skipped Deployments
Project Deployment Actions Updated (UTC)
grid-flow-builder Ignored Ignored Aug 10, 2026 4:56pm
grid-wallet-demo Ignored Ignored Aug 10, 2026 4:56pm

Request Review

@mintlify

mintlify Bot commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
Grid 🟢 Ready View Preview Aug 10, 2026, 4:58 PM

@github-actions

github-actions Bot commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

✱ Stainless preview builds for grid

This PR will update the grid SDKs with the following commit messages.

cli

docs(api): update client-public-key parameter descriptions in authcredential and authsession

go

docs(api): document client-held-key flow, deprecate legacy flow in auth

kotlin

chore(types): deprecate encryptedSessionSigningKey in AuthSession

openapi

fix(types): deprecate encryptedSessionSigningKey, update clientPublicKey validation in auth

php

docs(api): update auth credential and session signing key flow descriptions

python

docs(api): update auth credential and session key documentation for client-held-key flow

ruby

docs(api): update auth credentials and sessions flow documentation

typescript

docs(api): update auth credentials/sessions for client-held-key flow

Edit this comment to update them. They will appear in their respective SDK's changelogs.

⚠️ grid-typescript studio · code · diff

Your SDK build had a failure in the lint CI job, which is a regression from the base state.
generate ✅build ✅ (prev: build ⏭️) → lint ❗ (prev: lint ⏭️) → test ✅

npm install https://pkg.stainless.com/s/grid-typescript/eb9b39c663fa83b49a0911f2eccdec5a2b780889/dist.tar.gz
New diagnostics (1 note)
💡 Schema/DeprecatedWithoutMessage: Confirm schema is deprecated and add missing deprecation details if needed.
grid-openapi studio · code · diff

Your SDK build had at least one new note diagnostic, which is a regression from the base state.
generate ⚠️

New diagnostics (1 note)
💡 Schema/DeprecatedWithoutMessage: Confirm schema is deprecated and add missing deprecation details if needed.
grid-ruby studio · code · diff

Your SDK build had at least one new note diagnostic, which is a regression from the base state.
generate ✅build ✅ (prev: build ⏭️) → lint ✅test ✅

New diagnostics (1 note)
💡 Schema/DeprecatedWithoutMessage: Confirm schema is deprecated and add missing deprecation details if needed.
grid-go studio · code · diff

Your SDK build had at least one new note diagnostic, which is a regression from the base state.
generate ✅build ✅ (prev: build ⏭️) → lint ❗test ❗

go get github.com/stainless-sdks/grid-go@379ebba87d8228f0ad093d804bc15a16879af07c
New diagnostics (1 note)
💡 Schema/DeprecatedWithoutMessage: Confirm schema is deprecated and add missing deprecation details if needed.
grid-kotlin studio · code · diff

Your SDK build had at least one new note diagnostic, which is a regression from the base state.
generate ⚠️build ✅lint ✅test ❗

New diagnostics (1 note)
💡 Schema/DeprecatedWithoutMessage: Confirm schema is deprecated and add missing deprecation details if needed.
⚠️ grid-python studio · code · diff

Your SDK build had a failure in the lint CI job, which is a regression from the base state.
generate ✅build ✅ (prev: build ⏭️) → lint ❗ (prev: lint ⏭️) → test ❗

pip install https://pkg.stainless.com/s/grid-python/19dc0d1dff062ba9bb366fc99881a411b8a25939/grid-0.0.1-py3-none-any.whl
grid-php studio · code · diff

Your SDK build had at least one new note diagnostic, which is a regression from the base state.
generate ✅lint ✅test ✅

New diagnostics (1 note)
💡 Schema/DeprecatedWithoutMessage: Confirm schema is deprecated and add missing deprecation details if needed.
grid-cli studio · code · diff

Your SDK build had at least one "warning" diagnostic, but this did not represent a regression.
generate ⚠️build ❗lint ❗test ❗


This comment is auto-generated by GitHub Actions and is automatically kept up to date as you push.
If you push custom code to the preview branch, re-run this workflow to update the comment.
Last updated: 2026-08-10 17:02:15 UTC

@greptile-apps

greptile-apps Bot commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR makes client-held session signing keys the documented primary flow for OAuth, passkey, and session refresh while retaining the server-sealed key as a deprecated legacy path.

  • Accepts compressed SEC1 public keys in passkey challenge and refresh schemas.
  • Updates AuthSession semantics, endpoint examples, and generated OpenAPI bundles.
  • Expands the client-key guide to distinguish client-held and legacy key delivery.

Confidence Score: 4/5

The PR should not merge until the primary client-held-key guide provides a complete signing-key workflow; the refresh example inconsistency is also worth correcting.

The newly recommended workflow cannot be completed using the supplied platform examples because they generate legacy ECDH keys while subsequent signing requires compressed ECDSA key material, and the refresh example also shows conflicting encodings for one bound key.

Files Needing Attention: mintlify/snippets/global-accounts/client-keys.mdx; openapi/paths/auth/auth_sessions_{id}_refresh.yaml

Important Files Changed

Filename Overview
mintlify/snippets/global-accounts/client-keys.mdx Introduces the primary client-held-key workflow, but leaves all platform generation and signing examples incompatible or incomplete for that workflow.
openapi/paths/auth/auth_sessions_{id}_refresh.yaml Documents compressed-key refresh behavior, but the request and payloadToSign examples use inconsistent SEC1 encodings.
openapi/components/schemas/auth/AuthSession.yaml Deprecates encryptedSessionSigningKey and accurately describes its conditional legacy-only presence.
openapi/components/schemas/auth/AuthCredentialChallengeRequest.yaml Expands passkey challenge validation to accept compressed and uncompressed P-256 SEC1 keys.
openapi/components/schemas/auth/AuthSessionRefreshRequest.yaml Expands refresh validation and documents encoding-selected key delivery.
openapi/components/schemas/auth/OauthCredentialVerifyRequestFields.yaml Updates OAuth key-delivery documentation and its example without changing the pre-existing validation shape.
openapi/paths/auth/auth_credentials_{id}_verify.yaml Reframes OAuth and passkey session issuance around the client-held flow and updates the OAuth example.
openapi.yaml Generated bundle reflects the modular OpenAPI source changes.
mintlify/openapi.yaml Generated Mintlify OpenAPI bundle mirrors the root bundle.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
  A[Generate fresh P-256 signing key] --> B{clientPublicKey encoding}
  B -->|Compressed 02 or 03| C[Client-held flow]
  C --> D[Grid issues AuthSession without encryptedSessionSigningKey]
  D --> E[Client signs with retained private key]
  B -->|Uncompressed 04| F[Deprecated legacy flow]
  F --> G[Grid returns encryptedSessionSigningKey]
  G --> H[Client decrypts server-issued signing key]
  H --> I[Client signs with decrypted key]
Loading

Comments Outside Diff (1)

  1. openapi/paths/auth/auth_sessions_{id}_refresh.yaml, line 105 (link)

    P2 Refresh key encodings conflict

    The request example submits a compressed 02... key, but the corresponding payloadToSign still binds its uncompressed 04... representation even though the endpoint requires the same clientPublicKey on both calls. This leaves readers without a consistent representation to echo on the signed retry and can lead to a mismatched retry returning 401.

    Knowledge Base Used: Auth and SCA

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: openapi/paths/auth/auth_sessions_{id}_refresh.yaml
    Line: 105
    
    Comment:
    **Refresh key encodings conflict**
    
    The request example submits a compressed `02...` key, but the corresponding `payloadToSign` still binds its uncompressed `04...` representation even though the endpoint requires the same `clientPublicKey` on both calls. This leaves readers without a consistent representation to echo on the signed retry and can lead to a mismatched retry returning `401`.
    
    **Knowledge Base Used:** [Auth and SCA](https://app.greptile.com/lightspark/-/custom-context/knowledge-base/lightsparkdev/grid-api/-/docs/auth-and-sca.md)
    
    ---
    
    For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.
Prompt To Fix All With AI
### Issue 1
mintlify/snippets/global-accounts/client-keys.mdx:14
**Primary key workflow is unusable**

When an integrator follows the new client-held-key flow, the platform examples generate uncompressed ECDH keys while step 4 requires compressed ECDSA signing material. The Web key is also non-extractable, and the mobile signing examples require an unexplained raw scalar, so these examples cannot produce the valid `Grid-Wallet-Signature` needed to complete signed account actions.

### Issue 2
openapi/paths/auth/auth_sessions_{id}_refresh.yaml:105
**Refresh key encodings conflict**

The request example submits a compressed `02...` key, but the corresponding `payloadToSign` still binds its uncompressed `04...` representation even though the endpoint requires the same `clientPublicKey` on both calls. This leaves readers without a consistent representation to echo on the signed retry and can lead to a mismatched retry returning `401`.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Reviews (1): Last reviewed commit: "docs(auth): document client-held session..." | Re-trigger Greptile


In the **recommended client-held-key flow**, the key pair you generate in step 1 *is* your session key: you keep the private key on the device and use it directly to sign account actions. There is no session key to receive or decrypt, and Grid never transmits one — so the verify, refresh, and challenge responses omit `encryptedSessionSigningKey`. This is how `EMAIL_OTP` and `SMS_OTP` have always worked; `OAUTH` and `PASSKEY` now follow the same model.

To use it, send `clientPublicKey` in **compressed** SEC1 form — a 66-character hex string starting with `02` or `03` (the prefix followed by the 32-byte X coordinate). Grid treats a compressed key as a request to adopt it as the session signing key and returns an `AuthSession` with no `encryptedSessionSigningKey`. You then skip steps 2–3 and sign each `payloadToSign` with the private key you kept, exactly as in <a href="#4-sign-a-payloadtosign">step 4</a>. Because this key signs directly, generate it as a P-256 **signing** key (ECDSA) — not the ECDH recipient key the step 1 samples below create for the legacy decrypt flow.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Primary key workflow is unusable

When an integrator follows the new client-held-key flow, the platform examples generate uncompressed ECDH keys while step 4 requires compressed ECDSA signing material. The Web key is also non-extractable, and the mobile signing examples require an unexplained raw scalar, so these examples cannot produce the valid Grid-Wallet-Signature needed to complete signed account actions.

Context Used: mintlify/AGENTS.md (source)

Knowledge Base Used: Mintlify Documentation Site

Prompt To Fix With AI
This is a comment left during a code review.
Path: mintlify/snippets/global-accounts/client-keys.mdx
Line: 14

Comment:
**Primary key workflow is unusable**

When an integrator follows the new client-held-key flow, the platform examples generate uncompressed ECDH keys while step 4 requires compressed ECDSA signing material. The Web key is also non-extractable, and the mobile signing examples require an unexplained raw scalar, so these examples cannot produce the valid `Grid-Wallet-Signature` needed to complete signed account actions.

**Context Used:** mintlify/AGENTS.md ([source](https://github.com/lightsparkdev/grid-api/blob/main/mintlify/AGENTS.md))

**Knowledge Base Used:** [Mintlify Documentation Site](https://app.greptile.com/lightspark/-/custom-context/knowledge-base/lightsparkdev/grid-api/-/docs/mintlify-docs-site.md)

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

carsonp6 added a commit that referenced this pull request Aug 13, 2026
…ld-key flow (SP-3690)

The clientPublicKey encoding now selects the flow: compressed SEC1
(02/03 prefix, 66 hex) opts into the client-held session key model
documented for EMAIL_OTP/SMS_OTP, extended here to OAUTH and PASSKEY;
uncompressed SEC1 (04 prefix, 130 hex) keeps the deprecated legacy
HPKE-sealed flow. Relaxes the challenge/refresh clientPublicKey
patterns to accept both, switches representative examples to the
compressed form, and documents the new "Client-held session key"
flow in the client-keys.mdx guide. Also teaches the offramp scripts'
gen-keypair helper a --compressed flag for generating client-held
session keys.

Consolidates #811 into this PR.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@carsonp6

Copy link
Copy Markdown
Contributor Author

Consolidated into #817.

@carsonp6 carsonp6 closed this Aug 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant