AAuth -11: person token required, directed identity, fully-specified alg - #1
Merged
Conversation
whoami answers two questions at one endpoint, and now shows two of the five access modes rather than one. Agent identity access is unchanged in -11 and stays: an agent token with no ?scope= is verified per §Agent Token Verification and returns the agent's own iss and sub. The agent token is the only token a resource reads that still carries an agent identifier — -11 removed `agent` from person, resource and auth tokens, not from here. Person identity is the new work: - Add the aa-person+jwt branch: dwk, PS JWKS discovery, exp/iat, iss as a conforming server identifier, aud, and cnf.jwk against the request-signing key. A person token carrying scope or account is rejected. With no ?scope= the verified token is the answer; with ?scope= it becomes the basis of a resource token. - ?scope= on an agent token now gets 401 requirement=person-token: a resource MUST have verified a person token before it issues a resource token. Nothing is read or fetched from the agent token on that path — it would not change the answer, and following its iss would let an attacker drive outbound requests from the Worker. - Resource token: drop the `agent` claim, which -11 removed. Copy ps, sub and person_token_jti from the verified person token, keep agent_jkt, copy mission_s256 and tenant when present, and never expire after the person token does — the most a resource can do about mission expiry, since it sees only the hash. - A person's identity is the pair (iss, sub). sub is opaque and unique only within its issuer, so identityRecordKey length-prefixes iss and there is no path that produces a key from sub alone. iss is always released alongside sub. An agent identifier is a different shape — global and self-qualifying — and equally unparsed. - Emit and accept only the RFC 9864 fully-specified Ed25519; the polymorphic EdDSA is rejected. This is a flag day: Wallet's svr/issuer/sign.js must ship Ed25519 for AAuth token types in the same window. Separately, strip alg before importSigningKey — generate-key.mjs stamps alg "Ed25519" on the SIGNING_KEY secret and workerd's importKey rejects that on an OKP key. - openapi.json: rewrite for the full flow and declare access_mode agent-token, the minimum that gets an answer, so no agent skips this resource for a setup it does have. No x-aauth-access-mode annotation: R3 annotations are sparse and the one operation requires exactly the resource-wide mode. - Add vitest coverage: signed requests end to end, both identity paths, every verification failure mode, resource token shape, mission pass-through, and the directed-identifier rule. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FmiCqDjRUSx6zb1N4gZPXE
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ✅ Deployment successful! View logs |
whoami-aauth-dev | c63265f | Aug 13 2026, 03:07 PM |
This was referenced Aug 12, 2026
Merged
Merged
Merged
Conflict in src/index.ts resource-token header: kept branch side (SIGNING_ALG/TOKEN_TYP constants, exp clamped to person token). Main's fully-specified Ed25519, workerd alg-strip, and httpsig 2.0.1 are all already present on this branch. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TQ2FCHHAnuJWF5TJB3838S
whoami is a resource with a single bodyless GET endpoint, so AAuth -11 §10.3's content-digest rule (body-carrying requests to PS or AS endpoints) does not apply anywhere here: no requireContentDigest on verification, and the four-component Accept-Signature challenge is unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TQ2FCHHAnuJWF5TJB3838S
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
whoamianswers two questions at one endpoint, and now demonstrates two of the five access modes rather than one. Adds the person-identity path for AAuth -11, enforces directed identity properly, and moves to the fully-specifiedEd25519.What changed
Agent identity is unchanged and stays. An agent token with no
?scope=is verified per §Agent Token Verification and returns the agent's ownissandsub. The agent token is the only token a resource reads that still carries an agent identifier — -11 removedagentfrom person, resource and auth tokens, not from here.Person identity is the new work:
aa-person+jwtbranch:dwk, PS JWKS discovery,exp/iat,issas a conforming server identifier,aud, andcnf.jwkagainst the request-signing key. A person token carryingscopeoraccountis rejected. With no?scope=the verified token is the answer; with?scope=it becomes the basis of a resource token.?scope=on an agent token now gets401 requirement=person-token. A resource MUST have verified a person token before it issues a resource token. Nothing is read or fetched from the agent token on that path — it would not change the answer, and following itsisswould let an attacker drive outbound requests from the Worker.agentclaim; copiesps,subandperson_token_jtifrom the verified person token; keepsagent_jkt; copiesmission_s256andtenantwhen present; and never expires after the person token does — the most a resource can do about mission expiry, since it sees only the hash.(iss, sub).subis opaque and unique only within its issuer, soidentityRecordKeylength-prefixesiss, and there is no path that produces a key fromsubalone.issis always released alongsidesub. An agent identifier is a different shape — global and self-qualifying — and equally unparsed.Ed25519; the polymorphicEdDSAis rejected. Separately,algis stripped beforeimportSigningKey—generate-key.mjsstampsalg: "Ed25519"on theSIGNING_KEYsecret and workerd'simportKeyrejects that on an OKP key.openapi.jsonrewritten for the full flow, declaringaccess_mode: agent-token— the minimum that gets an answer, so no agent skips this resource for a setup it does have. Nox-aauth-access-modeannotation: R3 annotations are sparse and the one operation requires exactly the resource-wide mode.This is one coordinated wave
Twelve PRs across twelve repositories implement AAuth -11 and R3 -02, built in parallel worktrees that could not see each other and reconciled in one integration pass.
Merging any one alone breaks the others. For this repo, concretely:
HelloCoop/WalletshipsEd25519makeswhoamireject every live person token. Everyaa-person+jwtandaa-auth+jwtperson.hello.coopissues today carriesalg: EdDSA; this branch rejects it outright.whoamiwould refuse every token the live PS issues.aauth-dev/playgroundcallswhoamifirst in its browser walkthrough, and now fetches a person token before doing so. Merging one without the other leaves the demo narrating a hop the resource does not require, or requiring a hop the demo does not perform.mainauto-deploys. There is no staging.The twelve PRs
hellocoop/mockindickhardt/AAuthaauth-dev/packages-js@aauth/protocol1.0.0 (new),@aauth/agent3.0.0,@aauth/resource2.0.0, five more.aauth-dev/proxy@aauth/proxy1.0.0 — the agent-proxy core.hellocoop/aauth-proxyhellocoop/proxy-mcp@aauth/proxy^1.0.0; AP metadata cleanup.aauth-dev/notesaauth-dev/whoamiaauth-dev/web-agent-demo(playground)202deferred path.aauth-dev/playground-popupaauth-dev/registryaauth-dev/wwwRelated and already open:
aauth-dev/explorer#5 reworks the explorer for -11 / R3 -02.Background:
AAUTH-11-INTEGRATION.md(the integration ledger) andAAUTH-11-PACKAGE-CONTRACT.md(the pinned interface contract the parallel work packages were built against).whoamiholds no grants of its own, so it costs its users nothing directly. The wave's flag day does, and this repo is part of the same cutover window:version, so the bytes change and every hash changes.hellocoop/aauth-proxyrekeys its connection store from a baresubtopersonId(ps, sub)— correct under -11, since asubfrom one PS must never match a record established under another, and exactly the ruleidentityRecordKeyimplements here. Every existingtok:row is orphaned: its key can no longer be computed. Users reconnect every service, and six times for GitHub, which splits into six resources in the same wave.Ordering constraints
@aauth/protocol1.0.0 needs a manual first publish — npm will not register a trusted publisher for a name absent from the registry.@aauth/proxy1.0.0 must publish before the fleet servesaccess_mode: person-token. 0.4.0 hard-fails on an unrecognized value.HelloCoop/Walletsvr/issuer/sign.js:32must shipEd25519in the same window.const alg = useEdDSA ? 'EdDSA' : 'RS256',useEdDSA = isAAuthType(typ). RS256 stays for OIDC.Not covered by tests
expires_atclamp.mission_endpointis unimplemented by agreement. The reference PS accepts any value as amission_s256, so §Resource Token Verification step 7 (mission active, now beforeexpires_at) is never enforced. Mission pass-through is tested; mission expiry is not.environment: 'node', wherejoseaccepts the OKPalg: "Ed25519"that workerd'scrypto.subtle.importKeyrejects — breaking the strip passes CI and fails on deploy. That risk is live here:generate-key.mjsstampsalgon the secret this worker imports.wrangler dev. A[[routes]] custom_domainentry makes the dev server hand the Worker the production hostname, so@authoritynever matches what the client signed; httpsig reportsverified: falsewith noerrorstring, which reads like a bad key rather than a host mismatch.What a reviewer should check
src/aauth.tsis a deliberate local copy, written to be deleted once@aauth/resourcelands. It is still here:whoamiandnoteswere written while@aauth/resourcewas still being built, and the interface contract pinned@aauth/protocolcompletely while saying nothing about@aauth/resource. Moving onto the package is a follow-up, not part of this PR — and it needs@aauth/resourceto expose acreateResourceTokenthat a caller holding only a record of the person token can satisfy (AAuth#90).tenantcopy-through. Step 6 has the PS verifyps,sub,mission_s256andtenantmatch exactly, rejecting on any mismatch or omission. (Checked:whoamicopies it.)EdDSA. (Checked:EdDSAappears insrc/aauth.tsonly inside the forbidden-alg set, with a test assertingisForbiddenAlg('EdDSA')is true.)'person'where an auth token is required. A person token and a PS-issued auth token shareiss,dwk,aud,subandcnfand differ only intyp; without the check, a person token passes everything else and falls through to the scope gate as403 insufficient_scope— a wrong-credential problem reported as a permissions problem.alg-strip beforecrypto.subtle.importKeyis intact, on the signing key as well as verification keys.identityRecordKeylength-prefixesiss, and no code path derives a key fromsubalone.