Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion mintlify/global-accounts/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,6 @@ Some Global Accounts capabilities require platform enablement before you can bui
Generate the P-256 key pair, decrypt the session signing key, and sign payloads on Web, iOS, and Android.
</Card>
<Card title="Sandbox testing" href="/global-accounts/platform-tools/sandbox-testing" icon="hammer">
Magic values for OTP, signatures, and OAuth tokens that exercise the full request shape without standing up real auth providers.
Magic values for OTP and signatures, plus sandbox OIDC token rules that exercise the full request shape without standing up real auth providers.
</Card>
</CardGroup>
4 changes: 2 additions & 2 deletions mintlify/global-accounts/platform-tools/sandbox-testing.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ icon: "/images/icons/hammer.svg"

import SandboxGlobalAccountMagic from '/snippets/sandbox-global-account-magic.mdx';

The Grid sandbox lets you exercise the full Global Accounts integration — customer creation, account lookup, credential registration, funding, and signed withdrawals — without moving real money or standing up real auth providers. All API endpoints work the same way as in production, but money movements are simulated and real auth checks are bypassed via a small set of magic values.
The Grid sandbox lets you exercise the full Global Accounts integration — customer creation, account lookup, credential registration, funding, and signed withdrawals — without moving real money or standing up real auth providers. All API endpoints work the same way as in production, but money movements are simulated. OTP, passkey, and wallet signatures use sandbox-only magic values, while OAuth uses JWT-shaped sandbox OIDC tokens with claim, freshness, identity, and nonce checks.

## Sandbox setup

Expand Down Expand Up @@ -52,7 +52,7 @@ All webhook events fire normally in sandbox. Configure your webhook URL in the d
When you're ready to go live:

1. Generate production API tokens in the dashboard and swap them for the sandbox credentials in your environment.
2. Remove any sandbox magic values from your client and server code — production runs the real OTP, HPKE, WebAuthn, and ECDSA flows.
2. Remove sandbox magic values and unsigned sandbox OIDC tokens from your client and server code — production runs the real OTP, HPKE, WebAuthn, OIDC signature, and ECDSA flows.
3. Configure production webhook endpoints.
4. Test with small amounts first.

Expand Down
12 changes: 6 additions & 6 deletions mintlify/openapi.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 5 additions & 1 deletion mintlify/snippets/global-accounts/authentication.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -384,7 +384,11 @@ sequenceDiagram
IB-->>C: { encryptedSessionSigningKey, expiresAt }
```

Grid validates the OIDC token signature against the issuer's JWKS on every call and requires `iat` to be no more than **60 seconds** older than the request. Use a fresh token for each `verify` call; cached tokens will fail.
Grid validates the OIDC token signature against the issuer's JWKS on every call and requires `iat` to be no more than **60 seconds** older than the request. Use a fresh token for each `verify` call; cached tokens will fail. The token identity (`iss`, `aud`, and `sub`) must match the OAuth credential being verified.

<Note>
In sandbox, OAuth still uses JWT-shaped OIDC tokens. The sandbox skips real IdP signature verification, but it validates the same identity and freshness claims. For `verify`, include `nonce` equal to `sha256(clientPublicKey)`. See [Sandbox testing](/global-accounts/platform-tools/sandbox-testing#oauth-oidc-token).
</Note>

```bash
curl -X POST "$GRID_BASE_URL/auth/credentials" \
Expand Down
50 changes: 42 additions & 8 deletions mintlify/snippets/sandbox-global-account-magic.mdx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
The Grid sandbox accepts a small set of magic values that bypass real auth and credential checks for Global Account flows, so you can exercise the full request shape without standing up Turnkey, WebAuthn, or an OIDC provider. These values are sandbox-only — production enforces real signature verification, WebAuthn assertion, and OIDC nonce binding.
The Grid sandbox accepts a small set of magic values for Global Account flows, so you can exercise the full request shape without standing up Turnkey, WebAuthn, or an OIDC provider. OTP, passkey, and wallet signatures use fixed sandbox-only values. OAuth uses JWT-shaped sandbox OIDC tokens: sandbox skips real IdP signature verification, but still validates the token claims, freshness, credential identity, and verify-time nonce binding.

A wrong magic value (or any other value) returns `401 UNAUTHORIZED` with a `reason` field that names the specific check that failed.
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.

### Email OTP code

Expand Down Expand Up @@ -55,24 +55,58 @@ Any other signature returns `401 UNAUTHORIZED` with `reason: "Invalid passkey si

### OAuth (OIDC) token

Pass `sandbox-valid-oidc-token` as the body `oidcToken` on both `POST /auth/credentials` (OAUTH create) and `POST /auth/credentials/{id}/verify` (OAUTH).
OAuth does not use a fixed magic token in sandbox. Pass a JWT-shaped OIDC token as `oidcToken`. The JWT signature segment can be a dummy value, but the payload must look like a real ID token.

For `POST /auth/credentials` with `type: "OAUTH"`, the sandbox token must include:

- `iss`: a supported issuer, such as `https://accounts.google.com`, `accounts.google.com`, or `https://appleid.apple.com`
- `aud`: a non-empty string, or a single-element string array
- `sub`: a non-empty subject identifier for the user
- `iat`: a numeric issued-at timestamp no more than 60 seconds before the request, with 5 seconds of clock skew allowed
- `exp`: a numeric expiration timestamp later than the request time

Grid stores the OAuth credential's registered identity from `iss`, `aud`, and `sub`. On `POST /auth/credentials/{id}/verify`, the fresh `oidcToken` must carry the same `iss`, `aud`, and `sub` as the credential being verified. It must also include `nonce` equal to `sha256(clientPublicKey)`, where `clientPublicKey` is the exact hex public key sent in the verify request.
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.

P2 nonce hash encoding not specified — developers not using the Node.js example may compute it incorrectly

The prose says nonce must equal sha256(clientPublicKey) but doesn't say the output is hex-encoded. The code example uses .digest("hex"), producing a lowercase hex string. A developer writing a Python or Go integration might reach for base64 or raw bytes instead, producing a nonce the backend won't match, and getting a 401 with no obvious pointer to the encoding mismatch. Specifying "(hex-encoded)" inline removes the ambiguity.

Suggested change
Grid stores the OAuth credential's registered identity from `iss`, `aud`, and `sub`. On `POST /auth/credentials/{id}/verify`, the fresh `oidcToken` must carry the same `iss`, `aud`, and `sub` as the credential being verified. It must also include `nonce` equal to `sha256(clientPublicKey)`, where `clientPublicKey` is the exact hex public key sent in the verify request.
Grid stores the OAuth credential's registered identity from `iss`, `aud`, and `sub`. On `POST /auth/credentials/{id}/verify`, the fresh `oidcToken` must carry the same `iss`, `aud`, and `sub` as the credential being verified. It must also include `nonce` equal to `sha256(clientPublicKey)` (hex-encoded lowercase), where `clientPublicKey` is the exact hex public key sent in the verify request.
Prompt To Fix With AI
This is a comment left during a code review.
Path: mintlify/snippets/sandbox-global-account-magic.mdx
Line: 68

Comment:
**`nonce` hash encoding not specified — developers not using the Node.js example may compute it incorrectly**

The prose says `nonce` must equal `sha256(clientPublicKey)` but doesn't say the output is hex-encoded. The code example uses `.digest("hex")`, producing a lowercase hex string. A developer writing a Python or Go integration might reach for base64 or raw bytes instead, producing a nonce the backend won't match, and getting a 401 with no obvious pointer to the encoding mismatch. Specifying "(hex-encoded)" inline removes the ambiguity.

```suggestion
Grid stores the OAuth credential's registered identity from `iss`, `aud`, and `sub`. On `POST /auth/credentials/{id}/verify`, the fresh `oidcToken` must carry the same `iss`, `aud`, and `sub` as the credential being verified. It must also include `nonce` equal to `sha256(clientPublicKey)` (hex-encoded lowercase), where `clientPublicKey` is the exact hex public key sent in the verify request.
```

How can I resolve this? If you propose a fix, please make it concise.

Fix in Claude Code


```bash
export PUBLIC_KEY="04f45f2a22c908b9ce09a7150e514afd24627c401c38a4afc164e1ea783adaaa31d4245acfb88c2ebd42b47628d63ecabf345484f0a9f665b63c54c897d5578be2"
OIDC_TOKEN=$(node - <<'NODE'
const crypto = require("crypto");

const publicKey = process.env.PUBLIC_KEY || "04f45f2a22c908b9ce09a7150e514afd24627c401c38a4afc164e1ea783adaaa31d4245acfb88c2ebd42b47628d63ecabf345484f0a9f665b63c54c897d5578be2";
const now = Math.floor(Date.now() / 1000);
const b64url = (value) =>
Buffer.from(JSON.stringify(value)).toString("base64url");

const payload = {
iss: "https://accounts.google.com",
sub: "sandbox-user-123",
aud: "grid-sandbox-oauth-client-id",
iat: now,
exp: now + 300,
nonce: crypto.createHash("sha256").update(publicKey).digest("hex"),
email: "sandbox-user-123@example.com",
email_verified: true
};

console.log(
`${b64url({ alg: "RS256", typ: "JWT" })}.${b64url(payload)}.sandbox-signature`
);
NODE
)

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" \
-H "Request-Id: 7c4a8d09-ca37-4e3e-9e0d-8c2b3e9a1f21" \
-d '{
"type": "OAUTH",
"oidcToken": "sandbox-valid-oidc-token",
"clientPublicKey": "04f45f2a..."
"oidcToken": "'"$OIDC_TOKEN"'",
"clientPublicKey": "'"$PUBLIC_KEY"'"
}'
```

Any other token returns `401 UNAUTHORIZED` with `reason: "Invalid OIDC token"`.

<Note>
**OAUTH create still requires a JWT-shaped token.** On the initial `POST /auth/credentials` (OAUTH create), the `oidcToken` must be a structurally valid JWT (`header.payload.signature`) so Grid can decode the `iss` claim and resolve the provider name. The literal `sandbox-valid-oidc-token` works on `verify` but not on `create` — for `create`, sign your own dummy JWT with any payload that includes a recognized `iss` claim. The sandbox bypasses signature verification, not JWT structure parsing.
The old literal `sandbox-valid-oidc-token` is no longer accepted. Use a freshly generated sandbox JWT for both OAuth credential registration and OAuth verification. Production requires a real ID token from your provider and verifies the provider signature.
</Note>

### Wallet signature header
Expand Down
Loading