Skip to content

Support Vault JWT auth method for gateway deployments - #129

Open
yatuk wants to merge 1 commit into
hashicorp:mainfrom
yatuk:jwt-auth-support
Open

Support Vault JWT auth method for gateway deployments#129
yatuk wants to merge 1 commit into
hashicorp:mainfrom
yatuk:jwt-auth-support

Conversation

@yatuk

@yatuk yatuk commented Aug 7, 2026

Copy link
Copy Markdown

Closes #107.

Summary

Right now vault-mcp-server only authenticates to Vault with a static token, VAULT_TOKEN or X-Vault-Token. Behind a gateway that already terminates user OIDC (Toolhive, or any similar reverse proxy), that means every user reaches Vault as the same service account. Audit logs can't tell who actually made a request, the static token has to carry the union of everyone's permissions, and per-user policy at the Vault layer is impossible even if the gateway is doing its own authorization correctly.

This adds VAULT_AUTH_METHOD=jwt as an alternative. When set:

  1. VaultContextMiddleware reads the JWT from the configured header (Authorization: Bearer by default).
  2. It exchanges that JWT with Vault via POST /v1/auth//login, using the configured role.
  3. Vault does its own JWT verification (signature, issuer, audience, whatever the auth method is configured to check) and returns a token scoped to that role's policies.
  4. That token goes into the request context in place of a static one, so the rest of the request pipeline is unchanged.

The exchanged token is cached in memory, keyed by a hash of the JWT, until shortly before its lease expires (or a configured VAULT_AUTH_JWT_CACHE_TTL, whichever is shorter), so a session making several requests in a row doesn't hit /login every time.

Static-token mode is untouched and stays the default. VAULT_TOKEN and X-Vault-Token are only ignored when JWT mode is explicitly turned on. If the JWT is missing, or Vault rejects it, or Vault can't be reached, the request fails with 401, 403, or 503 and a reason. It does not fall back to a static token.

New env vars, matching what's proposed in the issue: VAULT_AUTH_METHOD, VAULT_AUTH_JWT_PATH (default jwt), VAULT_AUTH_JWT_ROLE, VAULT_AUTH_JWT_HEADER (default Authorization), VAULT_AUTH_JWT_CACHE_TTL (optional).

A couple of judgment calls

  • The issue suggests caching by the JWT's sub+iat or jti. I cached by a sha256 hash of the raw JWT instead, same pattern client.go already uses for session token hashes. It gets the same result (repeat use of the same token hits cache, a reissued token doesn't) without parsing or trusting unverified claims client-side.
  • Network failures reaching Vault during the exchange return 503, not 401/403, since that's a connectivity problem, not Vault rejecting the JWT. Vault-side rejections (bad JWT, no matching role, missing role, invalid credentials) return 401 or 403 depending on what Vault said.

Testing

  • go build ./... and go vet ./... are clean.
  • go test ./pkg/... passes, 4.5s.
  • New tests in pkg/client/jwt_auth_test.go cover: bearer token extraction (with/without prefix, empty, malformed), the login exchange against a mocked Vault server (success, custom mount path, 403, 400, unreachable server), and the cache (hits, cache bypass for short leases, independent entries per JWT, VAULT_AUTH_JWT_CACHE_TTL capping).
  • New tests in pkg/client/middleware_test.go exercise VaultContextMiddleware end to end in JWT mode: successful exchange, missing JWT header, Vault rejecting the login, and confirming a static VAULT_TOKEN header is ignored while JWT mode is active.
  • The only failing test locally is the Docker/e2e one, and that's because make isn't on PATH in my environment, unrelated to this change.

cc @gastoncan since this is your issue, would appreciate a look at whether this matches your Toolhive + Keycloak setup.

Adds VAULT_AUTH_METHOD=jwt as an alternative to the current static
VAULT_TOKEN / X-Vault-Token auth. In gateway-fronted deployments
(Toolhive, any OIDC-terminating reverse proxy) every user currently
reaches Vault as the same service-account identity, since only a
static token is supported. That collapses per-user RBAC into a
single shared identity and breaks audit attribution at the Vault
layer.

With JWT mode enabled, VaultContextMiddleware reads the incoming
Authorization: Bearer <jwt> header, exchanges it via Vault's
POST /v1/auth/<mount>/login (role + jwt), and injects the resulting
short-lived, user-scoped Vault token into the request context in
place of a static one. The exchanged token is cached in memory,
keyed by a hash of the JWT, until close to its lease expiry (or a
configured VAULT_AUTH_JWT_CACHE_TTL cap), so repeat requests from
the same session don't re-hit /login every time.

Static-token mode remains the default and is unchanged; the new
code path only runs when VAULT_AUTH_METHOD=jwt is set. A failed
exchange (missing JWT, Vault rejecting it, Vault unreachable)
returns 401/403/503 with a reason and never falls back to a static
token.

New env vars: VAULT_AUTH_METHOD, VAULT_AUTH_JWT_PATH,
VAULT_AUTH_JWT_ROLE, VAULT_AUTH_JWT_HEADER, VAULT_AUTH_JWT_CACHE_TTL.
@yatuk
yatuk requested a review from a team as a code owner August 7, 2026 17:37
@hashicorp-cla-app

hashicorp-cla-app Bot commented Aug 7, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

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.

Support Vault JWT auth method to enable per-user, short-lived token auth in gateway deployments

1 participant