Split credentials into a separate endpoint#8
Conversation
c9d5b19 to
2568479
Compare
Separate the identity-assertion surface (/agent/identity, /agent/identity/claim) from the credential surface (/oauth2/token, /oauth2/revoke). Registration mints a service-signed identity_assertion; agents exchange it at the token endpoint for an access_token (RFC 7523 JWT-bearer). Credentials no longer come back from /agent/identity or the claim ceremony. - Rename /agent/auth → /agent/identity (and claim sub-paths) - Add /oauth2/token (RFC 7523 JWT-bearer) and /oauth2/revoke (RFC 7009) - OAuth error envelope per RFC 6749 §5.2; Cache-Control: no-store + Pragma: no-cache on token-endpoint responses per RFC 6749 §5.1 - Clean up the Registration model: derive status, drop credential coupling - Add service-side ES256 signing key for service-issued identity_assertions - Discovery JSON: surface top-level OAuth fields; rename agent_auth fields to identity_endpoint / claim_endpoint; keep revocation_uri at the legacy /agent/auth/revoke path (the secevent rename comes with PR 3) - Update READMEs and AUTH.md to describe the new two-endpoint shape
2568479 to
d313bb3
Compare
|
@greptile |
Greptile SummaryThis PR separates identity registration (
Confidence Score: 3/5The core token exchange and scope-gating logic is sound, but the JWKS endpoint the CHANGELOG claims is published does not exist, and the registration-layer logout gap (previously flagged) is still open. The new token endpoint correctly gates anonymous pre-claim registrations, the key-verification path now uses the public key, and the Registration model refactor is clean. However, agent-services/src/routes/well-known.ts (missing JWKS route and jwks_uri discovery field) and agent-services/src/store.ts (revokeForDelegation does not invalidate the registration). Important Files Changed
|
Co-authored-by: Michael Hadley <michael.hadley@workos.com>
Co-authored-by: Michael Hadley <michael.hadley@workos.com>
|
@greptile |
| **Refresh.** When the access_token expires (`expires_in` seconds after issuance), re-call [Step 5](#step-5--exchange-the-assertion) with the same `identity_assertion`. When the identity assertion itself expires or `/oauth2/token` returns `invalid_grant`, restart at [Step 3](#step-3--register). There is no OAuth refresh_token in this flow — the two-step pattern replaces it. | ||
|
|
||
| If you get a 401 on a previously-working credential: drop it, restart at [Step 1](#step-1--discover). Do not stash the credential and retry. | ||
| If you get a 401 on a previously-working access_token: try [Step 5](#step-5--exchange-the-assertion) once with the current assertion. If that also fails, drop everything and restart at [Step 1](#step-1--discover). |
There was a problem hiding this comment.
"drop everything" doesn't feel very descriptive or helpful:
| If you get a 401 on a previously-working access_token: try [Step 5](#step-5--exchange-the-assertion) once with the current assertion. If that also fails, drop everything and restart at [Step 1](#step-1--discover). | |
| If you get a 401 on a previously-working access_token: try [Step 5](#step-5--exchange-the-assertion) once with the current assertion. If that also fails, restart the flow at [Step 1](#step-1--discover). |
There was a problem hiding this comment.
What about If that also fails, discard the expired identity assertion, and restart at [Step 1](#step-1--discover).
There was a problem hiding this comment.
Love it. Let's go with that!
Co-authored-by: Michael Hadley <michael.hadley@workos.com>
Co-authored-by: Michael Hadley <michael.hadley@workos.com>
Co-authored-by: Michael Hadley <michael.hadley@workos.com>
Co-authored-by: Michael Hadley <michael.hadley@workos.com>
The scope gate at /oauth2/token only checked status === "unclaimed", but the moment the agent calls /agent/identity/claim the status moves to "pending_claim" — and the gate fell through to the full scope set before the human had confirmed ownership. Tighten to status !== "claimed" so anonymous registrations stay capped until the OTP ceremony actually completes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- AUTH.md: clarify the 401-on-access_token recovery path (discard the expired assertion before restarting at Step 1). - CHANGELOG.md: write the v0.2.0 entry covering this PR — new /oauth2/token and /oauth2/revoke endpoints, the /agent/auth → /agent/identity rename, discovery layout, error envelope changes, and the pre-claim scope fix. - package.json: 0.1.0 → 0.2.0. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Stack
Summary
Separate the identity-assertion surface (
/agent/identity,/agent/identity/claim) from the credential surface (/oauth2/token,/oauth2/revoke). Registration mints a service-signedidentity_assertion; agents exchange it at the token endpoint for an access_token (RFC 7523 JWT-bearer). Credentials no longer come back from/agent/identityor the claim ceremony./agent/auth→/agent/identity(and claim sub-paths)/oauth2/token(RFC 7523 JWT-bearer) and/oauth2/revoke(RFC 7009)Cache-Control: no-store+Pragma: no-cacheon token-endpoint responses per RFC 6749 §5.1Registrationmodel: derive status, drop credential coupling at registration timeagent_authfields toidentity_endpoint/claim_endpoint; keeprevocation_uriat the legacy/agent/auth/revokepath (the secevent rename comes with the next PR)