docs(auth): document client-held session key as the primary Embedded Wallet flow - #811
docs(auth): document client-held session key as the primary Embedded Wallet flow#811carsonp6 wants to merge 1 commit into
Conversation
…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>
|
The latest updates on your projects. Learn more about Vercel for GitHub. 2 Skipped Deployments
|
|
Preview deployment for your docs. Learn more about Mintlify Previews.
|
✱ Stainless preview builds for gridThis PR will update the cli go kotlin openapi php python ruby typescript Edit this comment to update them. They will appear in their respective SDK's changelogs.
|
| 💡 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@379ebba87d8228f0ad093d804bc15a16879af07cNew 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 SummaryThis 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.
Confidence Score: 4/5The 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
|
| 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]
Comments Outside Diff (1)
-
openapi/paths/auth/auth_sessions_{id}_refresh.yaml, line 105 (link)Refresh key encodings conflict
The request example submits a compressed
02...key, but the correspondingpayloadToSignstill binds its uncompressed04...representation even though the endpoint requires the sameclientPublicKeyon both calls. This leaves readers without a consistent representation to echo on the signed retry and can lead to a mismatched retry returning401.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. |
There was a problem hiding this 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)
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.…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>
|
Consolidated into #817. |
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 toOAUTHandPASSKEYand reframes the server-sealedencryptedSessionSigningKeyas the legacy path being retired.The encoding of
clientPublicKeyselects the flow:02/03prefix, 66 hex chars) → recommended client-held-key flow. The client retains the private key as the session signing key; the response omitsencryptedSessionSigningKey.04prefix, 130 hex chars) → deprecated legacy flow. Grid seals the session signing key to the client public key and returns it asencryptedSessionSigningKey.Additive and non-breaking — no
info.versionbump.Changes
Deprecate the server-sealed key (
components/schemas/auth/AuthSession.yaml)encryptedSessionSigningKey:deprecated: true; description rewritten to legacy-only.expiresAtdescriptions 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).clientPublicKeyfield descriptions inOauthCredentialVerifyRequestFields.yaml,AuthCredentialChallengeRequest.yaml,AuthSessionRefreshRequest.yaml.clientPublicKeyexamples switched to the compressed encoding; refresh201example no longer showsencryptedSessionSigningKey.Accept compressed keys (validation, non-breaking)
AuthCredentialChallengeRequest.yamlandAuthSessionRefreshRequest.yaml:clientPublicKeypattern^04[0-9a-fA-F]{128}$→^(0[23][0-9a-fA-F]{64}|04[0-9a-fA-F]{128})$; challengeminLength130 → 66. (OauthCredentialVerifyRequestFields.yamlhad no pattern and already accepted both.)Narrative doc (
mintlify/snippets/global-accounts/client-keys.mdx, the "Client keys & signing" guide)Validation
make lint(redocly bundle + lint + spectral, Node 22): pass — "Woohoo! Your API description is valid." 0 spectral errors.mintlify/openapi.yamlbundles rebuilt vianpm run build:openapiand committed (identical).oasdiff breaking --fail-on ERRvsmain: 0 errors (2request-property-pattern-changedwarnings for the relaxed patterns; a relaxation is a superset, so no breaking-change label).