Repository navigation
Let resource servers look up memberships beyond their own organization, bound to the user's token - #265
Merged
Conversation
…dk:0.24.0, auth-react-provider:0.18.4, auth-ui:0.21.7, openapi-operations:0.5.0, auth-resource-server-codegen-templates:0.0.167, auth-server-sdk:0.34.0, trpc-backend-init:0.9.89, auth-server:0.57.0, e2e-auth-tests:0.12.15 - let resource servers look up memberships beyond their own organization, bound to the user's token
Client apps other than the auth server can never call /api/me/organizations:
only the auth server's own app may hold tokens for its audience. Resource
servers ask the auth server as their API server instead, but the role
endpoint only answered for the API server's own organization, so a
multi-tenant service (the schema registry) could check neither "my
organizations" nor membership of any customer organization.
- API servers gain organization_membership_visibility (migration 00045):
`owning_organization` (default, current behaviour) or `all_organizations`,
set only by platform admins via
PUT /api/apis/{api_server_id}/organization-membership-visibility (and an
admin-only card on the API server page).
- GET .../organizations/{organization_id}/members/{uid}/role answers for
other organizations when the grant is set AND X-Subject-Token carries an
active access token of that user for the calling API server (verified
with the introspection pipeline: keyset, revocation, consent, app-to-API
connection).
- New GET /api/resource-server/users/{uid}/organizations lists the user's
memberships the API server may see; X-Subject-Token always required.
- Server SDK: listUserOrganizations(), isUserInOrganization() subject_token
option; resolver principals gain listOrganizationMemberships() (new
AuthPrincipal field in openapi-operations) and forward the presented
token for both lookups.
- Client SDK: setApiServerOrganizationMembershipVisibility();
listMyOrganizationMemberships() rejects with
MyOrganizationMembershipsUnavailableError off the auth server instead of
requesting a token it can never get.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WdvoqYDdDzHXxxW4TJ4qTW
…plicitly before using it
The resource-server membership endpoints read X-Subject-Token with a bare
truthiness check. parseSubjectTokenHeader() now decides absent / malformed /
token up front: a JWE in compact serialization (five non-empty base64url
segments) of at most 4096 characters (access tokens live in cookies), whose
protected header is a JSON object naming RSA-OAEP-256 and carrying enc,
keyset_id and aud. Malformed values are a 400 in the endpoints'
{ success, error } envelope; the role endpoint parses before verifying the
assertion and refuses a malformed header even on own-organization lookups.
Well-formed tokens that do not verify stay 403.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WdvoqYDdDzHXxxW4TJ4qTW
…ation membership visibility at API server creation
POST /api/apis validates its body with the API server definition schema,
which now accepts organization_membership_visibility, so a creator could
send `all_organizations`. It was only dropped by omission (registerApiServer
inserts an explicit column list). The endpoint now refuses any value other
than `owning_organization` with 400, for platform admins too: the admin-only
PUT /api/apis/{api_server_id}/organization-membership-visibility stays the
only way to widen it.
The E2E spec pins that neither an organization owner (regular user) nor an
admin can create a widened API server, that nothing is stored when refused,
and that the default / omitted value still creates one at
`owning_organization`.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WdvoqYDdDzHXxxW4TJ4qTW
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.
Why
The schema registry can't show a user's organizations or check their membership in customer organizations:
useMyOrganizations()/listMyOrganizationMemberships()ask for an access token whose audience is the auth server itself. Only the auth server's own app may hold one (validate-audience.ts), so this fails on every other app ("Failed to acquire access token for audience 'https://auth.schemavaults.com'").GET /api/resource-server/organizations/{organization_id}/members/{uid}/roleanswers only for the organization that owns the calling API server. A multi-tenant service like the registry gets a 403 for every customer organization.Rather than an admin service account or handing auth-server tokens to the registry's browser, this adds one narrow, admin-granted capability to the existing API-server-to-auth-server channel, bound to the user's own token.
What changed
Auth server
API_SERVERS.organization_membership_visibility:owning_organization(the default, unchanged behaviour) orall_organizations. It is never set at creation.PUT /api/apis/{api_server_id}/organization-membership-visibility, for platform admins only (routeGuard: "admin"; refuses hardcoded API servers). There is also an admin-only switch card on/apis/[api_server_id].X-Subject-Tokencarries an active access token of that user for the calling API server. Lookups in the API server's own organization work exactly as before.GET /api/resource-server/users/{uid}/organizationslists the user's memberships that the API server may see (the/api/me/organizationsshape).X-Subject-Tokenis always required.verifyResourceServerSubjectToken()reuses the RFC 7662 introspection pipeline with the API server as the caller: the token must decrypt with that API server's keyset, not be revoked or older thantokens_valid_after, the user must still authorize the app, and the app must still be connected. A leaked JWKS access key alone therefore cannot enumerate anyone's memberships.Packages
app-definitions: the new field on the API server schema, plusgetOrganizationMembershipAccess()/getOrganizationMembershipVisibility().openapi-operations: new optionalAuthPrincipal.listOrganizationMemberships.auth-server-sdk: newlistUserOrganizations();isUserInOrganization(..., { subject_token }). Resolver principals gainlistOrganizationMemberships(), and both lookups forward the token the request presented (bearer or cookie).auth-client-sdk: newsetApiServerOrganizationMembershipVisibility(). On any app other than the auth server,listMyOrganizationMemberships()now rejects immediately withMyOrganizationMembershipsUnavailableErrorinstead of requesting a token it can never get.commit-changescascade.Docs: new
resource-server-organization-membershipsskill, a CLAUDE.md table row, and README updates for the SDK andopenapi-operations.Testing
bun run test: 22/22 workspace test tasks pass. New unit tests cover theapp-definitionshelpers, the SDK list/role helpers, the resolver forwarding the presented token (bearer and cookie), and the client SDK fail-fast and setter.bun run typecheck(24/24) andbun run lint(0 errors) pass.resource_management/ResourceServerOrganizationMembershipVisibility.cy.ts; the new paths are pinned inapi_contract/AuthServerOpenApiDocument.cy.ts. Not run locally (needs Docker), so CI will be its first run.createSchemaVaultsAuthResolvers()principal calling both lookups, and a subject token becoming invalid once its app is disconnected. The admin card server-renders with the saved state.Rollout
auth-server-sdk0.34.0,openapi-operations0.5.0,app-definitions0.15.0, ...).schemavaults-registrytoall_organizations.schemavaults/schemas) to list memberships server-side throughctx.auth.listOrganizationMemberships().🤖 Generated with Claude Code
https://claude.ai/code/session_01WdvoqYDdDzHXxxW4TJ4qTW
Generated by Claude Code