Skip to content

feat(auth): support distributed_claim_access_token on JWT login - #176

Merged
kurok merged 1 commit into
masterfrom
feat/175-distributed-claim-access-token
Sep 2, 2026
Merged

feat(auth): support distributed_claim_access_token on JWT login#176
kurok merged 1 commit into
masterfrom
feat/175-distributed-claim-access-token

Conversation

@kurok

@kurok kurok commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Summary

Closes #175.

Vault's JWT login API accepts an optional distributed_claim_access_token body parameter that VaultJwtAuth never 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_sources pointing at the Microsoft Graph API), and a mount whose provider_config sets fetch_groups skips 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 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.

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-jwt staleness 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, via hasOwnProperty plus 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

File Why
src/auth/VaultJwtAuth.js Constructor validation, __acquireDistributedClaimAccessToken() mirroring __acquireJwt(), conditional body key
index.d.ts JwtDistributedClaimConfig — the pair is mutually exclusive at compile time, with an explicit "neither" arm so every existing config still compiles
types/index.test-d.ts 3 valid cases, 4 @ts-expect-error cases
test/auth.jwt.test.mjs 20 cases across constructor validation, login body, provider lifecycle and combinations with each JWT source, plus log-hygiene and VaultClient dispatch cases
README.md New JWT subsection: when it is needed, the two keys, and the Vault-side setup
CHANGELOG.md Entry under # Unreleased

Notes for review

  • fetch_groups is a provider_config key on the auth mount's config (auth/jwt/config), not a role option — the role option that matters is groups_claim. The README says so explicitly, since that is where people tend to look. Verified against the JWT API docs.
  • A literal distributedClaimAccessToken is not validated — an empty string would be sent as "". This mirrors config.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.
  • No e2e coverage, as the issue anticipated: it would need Azure/Entra, which the offline suite cannot provide. test/conformance.vault-api.test.mjs has no JWT section at all (only Kubernetes, IAM and AppRole), so there was nothing to extend there.
  • No verbatim Vault error string is quoted in the new docs. The neighbouring bound_audiences section quotes strings taken from a live Vault; this was not run against Azure, so the failure is described as behaviour instead.

Type of change

  • Bug fix
  • New feature
  • Refactor
  • Documentation
  • CI / tooling

Checklist

  • Tests added or updated
  • npm run lint && npm test passes locally
  • User-facing changes recorded under # Unreleased in CHANGELOG.md
  • All commits have a Signed-off-by: trailer (git commit -s)

npm test was run against the docker compose Vault dev servers: 433 passing, 1 pending, including the JWT e2e suite. tsc -p types/tsconfig.json exits 0.

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>
@kurok
kurok requested review from m2broth and wRLSS as code owners September 2, 2026 07:52
@kurok
kurok merged commit ae32944 into master Sep 2, 2026
22 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

JWT auth: distributed_claim_access_token is not supported, so Azure roles with fetch_groups cannot be used

1 participant