Repository navigation
feat(jwtauth): WithTenantClaim names the claim the tenant is read from - #86
Merged
Merged
Conversation
A stock Socrate has no tenant model: a tenant reaches tokens only through a client's claim mapping, under Socrate's claims namespace, as https://socrate/tenant_id. The middleware read only a plain tenant_id, so httpware.RequireTenant refused every Socrate token. WithTenantClaim reads the named claim from the verified token instead: it replaces SocrateClaims.TenantID (so the revocation check sees it), a plain tenant_id is then ignored, a value that is not a UUID string (null included) is a 401, and an empty name rejects every token. The default is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GKRxaeYxyDhmt42cehLsGA
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.
What and why
jwtauthreads the tenant only from a plaintenant_idclaim, and a stock Socrate never issues one. Socrate has no tenant model, so a tenant reaches tokens only through a client's claim mapping ("tenant_id": "user.attributes.tenant_id"). Every mapped claim carries Socrate's claims namespace (NormalizeClaimsNamespacealways applies one; the default ishttps://socrate/), so the token carrieshttps://socrate/tenant_id. The middleware ignored that claim, the tenant stayeduuid.Nil, andhttpware.RequireTenantrefused every Socrate token on tenant-scoped routes. Lakebridge reported it on go-oauth2 #308.Changes:
jwtauth.WithTenantClaim(name)reads the tenant from the named claim of the verified token.SocrateClaims.TenantID, so aRevocationCheckersees the same tenant as the request context.tenant_idis then ignored.null) is a 401.RequireTenantrefuses the request, as before.Newlogs an error. It does not fall back totenant_id, matchingWithAudiences().SocrateClaimsas before. The named claim is then decoded from the payload only after signature and claim validation have passed, since a struct can't declare a claim whose name is only known at run time.docs/CLIENT-INTEGRATION.md: the §5 claim table, and the tenant isolation section, now with the three steps (user attribute, client mapping, option).The tenant still only ever comes from the signed token. Socrate's user attributes are written by global admins only (
PUT /api/admin/users/{id}/attributes, audited).How it was tested
New
jwtauth/tenant_claim_test.go, a table of 15 cases:tenant_idand ignores the namespaced claim;tenant_id;tenant_idis ignored when another claim is named;tenant_idbehaves like the default;nullare each rejected;Separate tests check that:
RevocationCheckersees the named tenant;Newlogs nothing for a named claim, and logs the fail-closed error for a blank name.The
nullcase caught a real gap during development:json.Unmarshalacceptsnullinto a string, so it is now rejected explicitly.go mod tidy && git diff --exit-code go.sumleavesgo.sumunchangedgo build ./...passesgo vet ./...passesgo test -race -count=1 -timeout=120s ./...passesgolangci-lint run ./...(v2.14.0, built with Go 1.27.1) reports no issuegovulncheck ./...: couldn't run here, because the sandbox can't reach vuln.go.dev. No dependency changed; CI runs it.## [Unreleased]inCHANGELOG.mdCompatibility
jwtauth.WithTenantClaim(name string) Option. No symbol was removed or renamed and no signature changed.tenant_id, with the same validation.🤖 Generated with Claude Code
https://claude.ai/code/session_01GKRxaeYxyDhmt42cehLsGA
Generated by Claude Code