Repository navigation
feat(core): adopter-token (JWT) authentication - #678
seanspeaks wants to merge 2 commits into
Conversation
Implement GetUserFromAdopterJwt with jose: JWKS (remote, cached once per process) or HMAC key, iss/aud checks, exp/nbf with tolerance, algorithm allow-list, and stable 401 reason codes. Find or create the individual and organization users; link only when the individual has no organization, and refuse a second organization with 403 multi_org_unsupported (ADR-056 §3.6). loadAppDefinition now fails at startup when authModes.adopterJwt is on without a usable jwtConfig. The schema's jwtConfig gains jwksUri, issuer, audience, algorithms, clockToleranceSec and roleClaim, and requires one of secret or jwksUri. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HyrF4oqcTdK2AtvcrocxE6
… stricter jwtConfig Classify verification failures by inversion: only known token failures are 401; anything else (refused connection, DNS failure, non-200 JWKS, generic JOSE error) is 503 jwks_unavailable. Validate jwksUri at load: https, or http only for loopback. Require audience with jwksUri and warn when issuer is unset. Extract AdopterTokenVerifier (config resolved once, JWKS held per verifier; module-global cache removed), a frozen error table with one fail() helper, assertSingleOrganization, and a find-or-create helper shared with GetUserFromXFriggHeaders. Add setOrgRole, UserConfig/AdopterJwtConfig types, schema oneOf and dependencies, and consolidate the tests under user/tests/adopter-jwt. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HyrF4oqcTdK2AtvcrocxE6
There was a problem hiding this comment.
If your organization's extra usage balance is empty, an organization admin can add extra usage credits at claude.ai/admin-settings/usage. If its monthly spend limit was reached, an admin can raise it on the same page. If neither applies, contact Anthropic support.
Once extra usage is available, someone with write access to this repository can comment @claude review on this pull request to trigger a review.
|
|
ADR reconciliation needed after Stack A/B — drafted with Claude Code at Sean's request Severity: must-reconcile (text only; the code looks consistent with the intent of ADR-056 §2).
Merge order: #671 → this PR. #644's ADR-034 edit is independent but should land before #643's code. |



Summary
This implements the adopter-token auth mode from ADR-056 §2, which until now was a stub that returned 501. Frigg can now verify tokens issued by an adopter's own identity provider directly, so a caller needs no shared secret and no relay. The first consumer is Left Hook's internal MCP server: admin.lefthook.com issues ES256 tokens, and frigg-internal verifies them against admin's JWKS. See lefthook.com ADR-0016 D6 and the build plan in lefthook.com PR #758.
This PR is stacked on #671 (
feat/management-api-v2). Canaries are published from this branch for development use until it merges down.What it does
user/adopter-token-verifier.jsis a new adapter that owns the config, the JWKS and the mapping from failures to reason codes. It is built once per router or dependency set, not once per request.jwksUri, and HS256 withsecret. Thejku/jwkheaders are never followed.expis required.issandaudare checked, andclockToleranceis bounded.jwks_unavailable, so a key-server outage does not log users out.GetUserFromAdopterJwttakes the user fromuserIdClaim(defaultsub) and the active org fromorgIdClaim, and exposes the role throughUser#getOrgRole().multi_org_unsupported. This check runs before anything is created. It is interim untilOrganizationMembershiplands in F4 (ADR-056 §3.6).adopter-jwt-config.js):secretorjwksUrimay be set.jwksUrimust be https; http is allowed only to loopback.audienceis required whenjwksUriis set, and a warning is logged whenissueris unset.oneOf, adependenciesrule and anotrule.user/app-user-helpers.jsholds the find-or-create andbuildUserlogic that both this path andGetUserFromXFriggHeadersuse. Each keeps its own linking policy.types/user/index.d.tsaddsUserConfig,AdopterJwtConfigandUser#getOrgRole/setOrgRole.jose@^5.10.0.Known, not fixed here
GetUserFromXFriggHeaderscomparesindividual.organizationUser, but the repositories returnorganizationId, so its mismatch check is always true. That will be tracked separately.Test plan
packages/corerun, with the no-op global setup because Mongo binaries can't run in CI-less containers: 15 suites and 63 tests fail. The failing tests match base by name, minus the 2 from the deleted stub suite. 2931 pass.packages/schemas: 154 pass.alg: none, HS256 signed with the public key, forgedjkuandjwkheaders, issuer mismatch.🤖 Generated with Claude Code
https://claude.ai/code/session_01HyrF4oqcTdK2AtvcrocxE6
Generated by Claude Code
📦 Published PR as canary version:
2.0.0--canary.678.58db4b2.0✨ Test out this PR locally via:
npm install @friggframework/admin-scripts@2.0.0--canary.678.58db4b2.0 npm install @friggframework/core@2.0.0--canary.678.58db4b2.0 npm install @friggframework/devtools@2.0.0--canary.678.58db4b2.0 npm install @friggframework/eslint-config@2.0.0--canary.678.58db4b2.0 npm install @friggframework/prettier-config@2.0.0--canary.678.58db4b2.0 npm install @friggframework/schemas@2.0.0--canary.678.58db4b2.0 npm install @friggframework/serverless-plugin@2.0.0--canary.678.58db4b2.0 npm install @friggframework/test@2.0.0--canary.678.58db4b2.0 npm install @friggframework/ui@2.0.0--canary.678.58db4b2.0 # or yarn add @friggframework/admin-scripts@2.0.0--canary.678.58db4b2.0 yarn add @friggframework/core@2.0.0--canary.678.58db4b2.0 yarn add @friggframework/devtools@2.0.0--canary.678.58db4b2.0 yarn add @friggframework/eslint-config@2.0.0--canary.678.58db4b2.0 yarn add @friggframework/prettier-config@2.0.0--canary.678.58db4b2.0 yarn add @friggframework/schemas@2.0.0--canary.678.58db4b2.0 yarn add @friggframework/serverless-plugin@2.0.0--canary.678.58db4b2.0 yarn add @friggframework/test@2.0.0--canary.678.58db4b2.0 yarn add @friggframework/ui@2.0.0--canary.678.58db4b2.0