feat(auth): support distributed_claim_access_token on JWT login - #176
Merged
Conversation
Vault's JWT login API accepts an optional `distributed_claim_access_token` body parameter, which VaultJwtAuth never sent. It only applies to the Azure (Entra ID) provider: past 200 groups Azure stops putting group membership in the token and emits OIDC distributed claims instead, and a mount whose `provider_config` sets `fetch_groups` skips the claim unconditionally. Either way Vault resolves the groups against the Microsoft Graph API itself, and the JWT being logged in with is not a credential for that call — so an Azure role relying on group lookups could not be used through this client. Two optional, mutually exclusive `config` keys now supply it: * `distributedClaimAccessToken` — a literal Graph access token. * `distributedClaimAccessTokenProvider` — an (optionally async) function invoked fresh at login time, never at construction and never cached, mirroring `jwtProvider`. Graph access tokens last about an hour, so the literal form inherits the literal-`jwt` staleness caveat and is wrong for anything longer-lived than a one-shot script. Purely additive. With neither key set the login body is byte-for-byte what it was before — the key is absent rather than undefined — so no existing configuration changes behaviour. The access token is treated as a credential and never logged; only its source is. Signed-off-by: kurok <22548029+kurok@users.noreply.github.com>
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.
Summary
Closes #175.
Vault's JWT login API accepts an optional
distributed_claim_access_tokenbody parameter thatVaultJwtAuthnever sent, so Azure/Entra roles that resolve group membership could not be used through this client.It applies only to the Azure (Entra ID) provider. Past 200 groups Azure stops putting group membership in the token and emits OIDC distributed claims instead (
_claim_names/_claim_sourcespointing at the Microsoft Graph API), and a mount whoseprovider_configsetsfetch_groupsskips the claim unconditionally and always asks Graph. Either way Vault calls Graph itself, and the JWT being logged in with is not a credential for that call — the login fails at the group-fetch step, after the JWT has already validated, so the failure names the group lookup rather than the token.Two optional, mutually exclusive
configkeys now supply it:distributedClaimAccessToken— a literal Graph access token.distributedClaimAccessTokenProvider— an (optionally async) function invoked fresh at login time, never at construction and never cached, mirroringjwtProvider.The provider variant was the open question in the issue. It is included because an Entra access token lasts about an hour, so the literal form inherits exactly the literal-
jwtstaleness caveat the README already documents — fine for a one-shot script, wrong for a service.Backward compatibility. Purely additive. With neither key set the login body is byte-for-byte what it was before: the key is absent, not
undefined. That is asserted directly, viahasOwnPropertyplus a deep-equal against the exact pre-#175 body. The access token is treated as a credential and never logged — only its source (literal/provider) is.Changes
src/auth/VaultJwtAuth.js__acquireDistributedClaimAccessToken()mirroring__acquireJwt(), conditional body keyindex.d.tsJwtDistributedClaimConfig— the pair is mutually exclusive at compile time, with an explicit "neither" arm so every existing config still compilestypes/index.test-d.ts@ts-expect-errorcasestest/auth.jwt.test.mjsVaultClientdispatch casesREADME.mdCHANGELOG.md# UnreleasedNotes for review
fetch_groupsis aprovider_configkey on the auth mount's config (auth/jwt/config), not a role option — the role option that matters isgroups_claim. The README says so explicitly, since that is where people tend to look. Verified against the JWT API docs.distributedClaimAccessTokenis not validated — an empty string would be sent as"". This mirrorsconfig.jwt, which is also unvalidated as a literal and fails at Vault. Only the provider's resolved value is checked for being a non-empty string. Happy to tighten if preferred.test/conformance.vault-api.test.mjshas no JWT section at all (only Kubernetes, IAM and AppRole), so there was nothing to extend there.bound_audiencessection quotes strings taken from a live Vault; this was not run against Azure, so the failure is described as behaviour instead.Type of change
Checklist
npm run lint && npm testpasses locally# UnreleasedinCHANGELOG.mdSigned-off-by:trailer (git commit -s)npm testwas run against thedocker composeVault dev servers: 433 passing, 1 pending, including the JWT e2e suite.tsc -p types/tsconfig.jsonexits 0.