Add auth session refresh endpoints#451
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
This stack of pull requests is managed by Graphite. Learn more about stacking. |
✱ Stainless preview buildsThis PR will update the kotlin openapi python typescript Edit this comment to update them. They will appear in their respective SDK's changelogs. ✅ grid-python studio · code · diff
✅ grid-kotlin studio · code · diff
✅ grid-typescript studio · code · diff
✅ grid-openapi studio · code · diff
This comment is auto-generated by GitHub Actions and is automatically kept up to date as you push. |
Greptile SummaryAdds a two-step mid-session reauthentication flow (
Confidence Score: 4/5The change is additive — two new endpoints and one new schema — and does not touch any existing endpoint logic. Safe to merge. The signed-retry flow is correctly structured with the stamp carried in headers and the session ID in the path. The only gaps are documentation quality: the challenge endpoint's 401 description is bare compared to every other auth endpoint, and the minLength/maxLength on the public key schema are redundant with the regex. Neither affects runtime behaviour. openapi/paths/auth/auth_sessions_{id}_challenge.yaml — the 401 response description would benefit from the same level of detail as its sibling verify endpoint.
|
| Filename | Overview |
|---|---|
| openapi/paths/auth/auth_sessions_{id}_challenge.yaml | New POST endpoint for issuing a mid-session reauthentication challenge; well-structured but the 401 error description is bare ("Unauthorized") with no detail about trigger conditions, unlike peer endpoints. |
| openapi/paths/auth/auth_sessions_{id}_verify.yaml | New POST endpoint for completing mid-session reauthentication; thorough 401 description, correct 201 response, required headers (Grid-Wallet-Signature, Request-Id) all documented. No issues found. |
| openapi/components/schemas/auth/AuthSessionReauthRequest.yaml | New schema for the shared request body; pattern correctly enforces SEC1 uncompressed P-256 format, but minLength/maxLength are redundant with the pattern constraint. |
| openapi/components/schemas/auth/AuthSession.yaml | Description updated to reference the new session verify endpoint alongside the existing credential verify; change is accurate and well-worded. |
| .stainless/stainless.yml | Wires challenge and verify methods under auth.sessions with correct endpoints and body_param_name; models section correctly registers the three new/reused schemas. |
| openapi/openapi.yaml | Root spec correctly adds $ref entries for the two new path files in the right position between auth_sessions_{id} and /agents. |
| openapi.yaml | Generated bundle; changes are consistent with the source spec additions in openapi/. |
| mintlify/openapi.yaml | Generated Mintlify bundle; correctly mirrors the source spec additions including both new paths and the AuthSessionReauthRequest schema. |
Sequence Diagram
sequenceDiagram
participant Client
participant Grid API
participant Turnkey
Note over Client,Grid API: Session approaching expiry (still active)
Client->>Grid API: POST /auth/sessions/{id}/challenge<br/>BasicAuth + clientPublicKey
Grid API->>Turnkey: Build CREATE_READ_WRITE_SESSION_V2 payload<br/>(bind clientPublicKey)
Grid API-->>Client: 202 SignedRequestChallenge<br/>(payloadToSign, requestId, expiresAt)
Note over Client: Sign payloadToSign with<br/>current session signing key
Client->>Grid API: POST /auth/sessions/{id}/verify<br/>BasicAuth + Grid-Wallet-Signature + Request-Id + clientPublicKey
Grid API->>Grid API: Verify signature matches challenge<br/>Verify requestId unexpired<br/>Verify clientPublicKey matches
Grid API->>Turnkey: Execute CREATE_READ_WRITE_SESSION_V2
Grid API-->>Client: 201 AuthSession<br/>(encryptedSessionSigningKey sealed to clientPublicKey)
Note over Client: Decrypt encryptedSessionSigningKey<br/>Use new signing key for future requests
Prompt To Fix All With AI
Fix the following 2 code review issues. Work through them one at a time, proposing concise fixes.
---
### Issue 1 of 2
openapi/paths/auth/auth_sessions_{id}_challenge.yaml:57-62
**Sparse `401` description inconsistent with the rest of the auth flow**
The challenge endpoint's `401` response is documented as only "Unauthorized", giving API consumers no guidance on what conditions trigger it. The `POST /auth/credentials/{id}/verify` and `POST /auth/sessions/{id}/verify` endpoints both enumerate concrete trigger conditions (expired OTP, bad signature, mismatched `clientPublicKey`, etc.). An expired session hitting this endpoint would presumably also return `401`, but there is nothing here to tell clients that — or to distinguish it from a bad `BasicAuth` credential. Developers integrating the challenge step will have to discover error conditions by trial and error.
### Issue 2 of 2
openapi/components/schemas/auth/AuthSessionReauthRequest.yaml:14-16
The `minLength` and `maxLength` constraints are redundant — `pattern: "^04[0-9a-fA-F]{128}$"` already anchors to exactly 130 characters. Redundant validators are harmless but can confuse validators that surface all applicable failures, making the schema harder to maintain.
```suggestion
pattern: "^04[0-9a-fA-F]{128}$"
```
Reviews (1): Last reviewed commit: "Add auth session refresh endpoints" | Re-trigger Greptile
| summary: Session reauthentication challenge | ||
| value: | ||
| payloadToSign: '{"type":"ACTIVITY_TYPE_CREATE_READ_WRITE_SESSION_V2","timestampMs":"1746736509954","organizationId":"org_abc123","parameters":{"targetPublicKey":"04f45f2a22c908b9ce09a7150e514afd24627c401c38a4afc164e1ea783adaaa31d4245acfb88c2ebd42b47628d63ecabf345484f0a9f665b63c54c897d5578be2"}}' | ||
| requestId: Request:019542f5-b3e7-1d02-0000-000000000010 | ||
| expiresAt: '2026-04-08T15:35:00Z' | ||
| '400': |
There was a problem hiding this comment.
Sparse
401 description inconsistent with the rest of the auth flow
The challenge endpoint's 401 response is documented as only "Unauthorized", giving API consumers no guidance on what conditions trigger it. The POST /auth/credentials/{id}/verify and POST /auth/sessions/{id}/verify endpoints both enumerate concrete trigger conditions (expired OTP, bad signature, mismatched clientPublicKey, etc.). An expired session hitting this endpoint would presumably also return 401, but there is nothing here to tell clients that — or to distinguish it from a bad BasicAuth credential. Developers integrating the challenge step will have to discover error conditions by trial and error.
Prompt To Fix With AI
This is a comment left during a code review.
Path: openapi/paths/auth/auth_sessions_{id}_challenge.yaml
Line: 57-62
Comment:
**Sparse `401` description inconsistent with the rest of the auth flow**
The challenge endpoint's `401` response is documented as only "Unauthorized", giving API consumers no guidance on what conditions trigger it. The `POST /auth/credentials/{id}/verify` and `POST /auth/sessions/{id}/verify` endpoints both enumerate concrete trigger conditions (expired OTP, bad signature, mismatched `clientPublicKey`, etc.). An expired session hitting this endpoint would presumably also return `401`, but there is nothing here to tell clients that — or to distinguish it from a bad `BasicAuth` credential. Developers integrating the challenge step will have to discover error conditions by trial and error.
How can I resolve this? If you propose a fix, please make it concise.| pattern: "^04[0-9a-fA-F]{128}$" | ||
| minLength: 130 | ||
| maxLength: 130 |
There was a problem hiding this comment.
The
minLength and maxLength constraints are redundant — pattern: "^04[0-9a-fA-F]{128}$" already anchors to exactly 130 characters. Redundant validators are harmless but can confuse validators that surface all applicable failures, making the schema harder to maintain.
| pattern: "^04[0-9a-fA-F]{128}$" | |
| minLength: 130 | |
| maxLength: 130 | |
| pattern: "^04[0-9a-fA-F]{128}$" |
Prompt To Fix With AI
This is a comment left during a code review.
Path: openapi/components/schemas/auth/AuthSessionReauthRequest.yaml
Line: 14-16
Comment:
The `minLength` and `maxLength` constraints are redundant — `pattern: "^04[0-9a-fA-F]{128}$"` already anchors to exactly 130 characters. Redundant validators are harmless but can confuse validators that surface all applicable failures, making the schema harder to maintain.
```suggestion
pattern: "^04[0-9a-fA-F]{128}$"
```
How can I resolve this? If you propose a fix, please make it concise.c2ec444 to
24bdfeb
Compare
f4789cb to
bc0650b
Compare
✱ Stainless preview builds for gridThis PR will update the kotlin openapi python typescript
|
24bdfeb to
679c965
Compare
bc0650b to
518683c
Compare
518683c to
df8713f
Compare
679c965 to
5825ec7
Compare
5825ec7 to
75d2847
Compare
df8713f to
f28efc6
Compare
pengying
left a comment
There was a problem hiding this comment.
Wait why do you need a challenge and verify on sessions in addition to the ones on credentials?
75d2847 to
e1c65a5
Compare
e1c65a5 to
4adb176
Compare
4c1aa5d to
b0d147f
Compare
4adb176 to
aadbb4a
Compare
4808147 to
ee39422
Compare
b0d147f to
def6040
Compare
|
this allows a user to get a new session using their session token rather than having to retrieve an otp code or hit a passkey or sign in via oauth again. It's simpler re-auth with an existing token |
def6040 to
5f54cd7
Compare
ee39422 to
f29ff4e
Compare
5f54cd7 to
d588311
Compare
|
do you feel like it's challenge verify here or is it just a refresh that needs a signature? one option could be to use a single endpoint with the 202 flow eg
No security concerns with the client continually refreshing the session here right? |
508e19d to
c761ec2
Compare
c761ec2 to
d56500e
Compare
|
I like your idea, it's cleaner than the way I had it |
|
updated the PR to use /auth/sessions/{id}/refresh |


Summary
POST /auth/sessions/{id}/refreshAuthSessionRefreshRequestand reuseSignedRequestChallengefor the initial202responserefreshmethod into Stainless and regenerate bundled OpenAPI specsEndpoint shape
Initial call
{ "clientPublicKey": "04f45f2a22c908b9ce09a7150e514afd24627c401c38a4afc164e1ea783adaaa31d4245acfb88c2ebd42b47628d63ecabf345484f0a9f665b63c54c897d5578be2" }Returns
202withSignedRequestChallenge:{ "payloadToSign": "{\"type\":\"ACTIVITY_TYPE_CREATE_READ_WRITE_SESSION_V2\",\"timestampMs\":\"1746736509954\",\"organizationId\":\"org_abc123\",\"parameters\":{\"targetPublicKey\":\"04f45f2a22c908b9ce09a7150e514afd24627c401c38a4afc164e1ea783adaaa31d4245acfb88c2ebd42b47628d63ecabf345484f0a9f665b63c54c897d5578be2\"}}", "requestId": "Request:019542f5-b3e7-1d02-0000-000000000010", "expiresAt": "2026-04-08T15:35:00Z" }Signed retry
{ "clientPublicKey": "04f45f2a22c908b9ce09a7150e514afd24627c401c38a4afc164e1ea783adaaa31d4245acfb88c2ebd42b47628d63ecabf345484f0a9f665b63c54c897d5578be2" }Returns
201withAuthSession:{ "id": "Session:019542f5-b3e7-1d02-0000-000000000011", "accountId": "InternalAccount:019542f5-b3e7-1d02-0000-000000000002", "type": "EMAIL_OTP", "encryptedSessionSigningKey": "w99a5xV6A75TfoAUkZn869fVyDYvgVsKrawMALZXmrauZd8hEv66EkPU1Z42CUaHESQjcA5bqd8dynTGBMLWB9ewtXWPEVbZvocB4Tw2K1vQVp7uwjf", "nickname": "example@lightspark.com", "createdAt": "2026-04-08T15:30:01Z", "updatedAt": "2026-04-08T15:35:00Z", "expiresAt": "2026-04-08T15:50:00Z" }The original session must still be active on both calls. If it has expired, clients should use the credential reauthentication flow.
Validation
npm run lint:openapi