Mint, verify, and use a SVID
Enterprise
Appropriate Vault Enterprise license required
You can use a JWT-SVID minted by Vault to authenticate with services that support SPIFFE.
If you are not familiar with SPIFFE, refer to the SPIFFE overview to learn more about how the SPIFFE secrets engine works in Vault.
Follow this document as written for a working example of the Vault SPIFFE secrets engine. Following these steps on production hosts will change their configuration. Refer to the setup guide for general configuration instructions.
Vault and the relying party used in this doc both run as local containers for simplicity.
Requirements
- An active Vault Enterprise license.
- Docker or Docker Desktop installed.
- Common command-line tools available in your shell, including
curl,jq,awk,cut,tr, andbase64.
Set up Vault
This section walks through the minimum configuration needed to get a workload authenticating with a Vault-minted JWT-SVID. It is not a SPIFFE production or hardening guide.
Export your Vault Enterprise license as an environment variable.
$ export VAULT_LICENSE=C10WMSH0W...snip...S3ASM3STR33TStart Vault.
$ docker run -d --name vault -p 8200:8200 \ -e VAULT_DEV_ROOT_TOKEN_ID=root \ -e VAULT_LICENSE=$VAULT_LICENSE \ hashicorp/vault-enterprise:latest \ server -devExport environment variables to access Vault.
$ export VAULT_ADDR="http://127.0.0.1:8200" VAULT_TOKEN="root"Enable the engine.
$ vault secrets enable spiffe Success! Enabled the spiffe secrets engine at: spiffe/Configure the trust domain.
$ vault write spiffe/config trust_domain=example.orgCreate a role.
$ vault write spiffe/role/workload \ template='{"sub": "/payments-api"}' \ ttl=1hsubrepresents a path. When Vault mints the JWT-SVID, it prefixes the configured trust domain and expands the value tospiffe://example.org/payments-api.Mint a JWT-SVID.
$ JWT_SVID=$(vault write -format=json \ spiffe/role/workload/mintjwt \ audience=example-relying-party | jq -r '.data.token') \ && echo $JWT_SVIDReview the JWT claims.
$ echo "$JWT_SVID" | cut -d. -f2 | tr '_-' '/+' | \ awk '{ while (length($0) % 4) $0 = $0 "="; print }' | \ base64 -d 2>/dev/null | jq .The SVID includes
subset tospiffe://example.org/payments-api, plusiss,aud,iat,exp, andvault.entity.id.Example output:
{ "aud": [ "example-relying-party" ], "exp": 1786629520, "iat": 1786629220, "iss": "http://0.0.0.0:8200/v1/spiffe", "nbf": 1786629220, "sub": "spiffe://example.org/payments-api", "vault": { "entity": { "id": "" } } }Review the secrets engine OIDC configuration.
$ curl -s $VAULT_ADDR/v1/spiffe/.well-known/openid-configuration | jq .Example output:
{ "issuer": "http://0.0.0.0:8200/v1/spiffe", "jwks_uri": "http://0.0.0.0:8200/v1/spiffe/.well-known/keys", "response_types_supported": [ "id_token" ], "subject_types_supported": [ "public" ], "id_token_signing_alg_values_supported": [ "RS256", "RS384", "RS512", "ES256", "ES384", "ES512" ] }The OIDC discovery document includes metadata about the issuer such as the issuer URL, where to find the keys (
jwks_uri), and which signing algorithms are supported. An OIDC-aware client fetches the discovery document first to determine how to validate tokens from this issuer.Review the secrets engine published keys.
$ curl -s $VAULT_ADDR/v1/spiffe/.well-known/keys | jq .Example key output:
{ "keys": [ { "use": "sig", "kty": "RSA", "kid": "04b6c36a-b0f9-b2da-5b42-2f96d324c858", "alg": "RS256", "n": "t6bhERg7sgITXoOsOATC94Pjw3_d1NCuQ3xQms0qwSMqk1boWRkteQ8CJdI5upY_n9CMQP7LjwKpmv04SHxL9qtxZr5ur9kx78ww0AUy8CWambFjPBf3H7xHS1oPrbXL_ZzeklwxJVI9OQdgRT7ysvifWItqPNiGH6skUqQcjLf0wo3cNCypixAmsU7liKqFtBsd-dlhgwlG7EGeYOQMhWJRF13ifAsml073mPG3s4lMqjUwIbajh3guB1dIoN7tUx7xrEZwlEn2ouNjxbTzrW_tvGrO9WrJqW0JtFibrA2GmT26XC5v4GOsAF-PVYu4AD07crHG_2rkFNo8vDeopQ", "e": "AQAB" }, { "use": "sig", "kty": "RSA", "kid": "caeae134-948b-4364-8caa-ca951d572ffa", "alg": "RS256", "n": "zitKBBClfnXbTQS3eUYrhm2rz6QO5Sg0ybp5XqLYwVIw7lMPJk8D3tLa98AlCncvMo-g6lVRI57qYxJKKAASv5UpEm3PRKMqVdlIeXLSSRnPWteuaZ4wogHxy437RD17Mp8jCPGIfu20r6gZ2vNvd0LVqLJrntttq5ewFsTTvzoKbRizcy5WIT3O_rJfJVxap3Pk3SL0t8kLed2DN-l2ZMtvce4edWcq6LSIL8UgkM9AWl6DAr_IMBMiHRnqGVrek7E1UZmcuehThjy0ySewDBZLVu6OZ9iOineXSstpWA6kqF3Kr0-Sp_Po1jqZj5cOarDFC-yNSNUYuMSgu_K4Aw", "e": "AQAB" } ] }These are the public keys used to verify the JWT-SVID signature.
An OIDC-compatible JWT library reads OIDC configuration for related metadata, and the JWKS from
.well-known/keysand verifies the JWT.
Log in with the JWT-SVID
To see the JWT-SVID accepted by something other than Vault, run Grafana with its built-in JWT auth pointed at the engine's JWKS endpoint. Grafana's JWT auth needs only a JWKS URL.
Write the JWKS keys from Vault to a file.
$ curl -s $VAULT_ADDR/v1/spiffe/.well-known/keys | tee jwks.json | jq .Grafana requires HTTPS for
jwk_set_url. This step allows you to start Grafana by providing the file directly. In a production environment, you would do this over a TLS connection directly to Vault.Start Grafana.
$ docker run -d --name grafana -p 3000:3000 \ -v "$(pwd)/jwks.json:/etc/grafana/jwks.json:ro" \ -e "GF_AUTH_JWT_ENABLED=true" \ -e "GF_AUTH_JWT_HEADER_NAME=X-JWT-Assertion" \ -e "GF_AUTH_JWT_JWK_SET_FILE=/etc/grafana/jwks.json" \ -e "GF_AUTH_JWT_USERNAME_CLAIM=sub" \ -e "GF_AUTH_JWT_AUTO_SIGN_UP=true" \ grafana/grafana-oss:latestLog into Grafana with the SVID minted by Vault.
$ curl -H "X-JWT-Assertion: $JWT_SVID" http://localhost:3000/api/user | jqGrafana accepts the JWT-SVID as a valid credential. A successful response returns the logged-in user, with
loginset to the SPIFFE ID from thesubclaim.Example output:
{ "id": 2, "uid": "ffv2e0k8lrpq8e", "email": "spiffe://example.org/payments-api", "name": "", "login": "spiffe://example.org/payments-api", "theme": "", "orgId": 1, "isGrafanaAdmin": false, "isDisabled": false, "isExternal": true, "isExternallySynced": true, "isGrafanaAdminExternallySynced": false, "authLabels": [ "JWT" ], "updatedAt": "2026-08-13T14:28:22Z", "createdAt": "2026-08-13T14:28:22Z", "avatarUrl": "/avatar/c63487a5cd574fa9137173ec4d919b56", "isProvisioned": false }The output shows that Grafana maps the
subclaim from your JWT-SVID to theemailandloginfields. Grafana verifies the signature against the JWKS and then appliesuser_claim=subto pull the identity from theJWT_SVIDtoken that you passed in theX-JWT-Assertionheader.
Next steps
- Refer to the SPIFFE setup guide for general configuration guidance.
- Refer to the SPIFFE overview for architecture and feature details.
- Refer to the SPIFFE secrets engine API documentation for endpoint details.