Vault Authentication - Auth Methods: Token, AppRole, LDAP, Kubernetes
Status: Active
Last Updated: 2026-08-14
Category: Security - Phase 4: Secret & Access Management
Prerequisites: vault-introduction, rbac-basics
Time: 3-4 hours
Tags: vault, authentication, approle, ldap, kubernetes, oidc, tokens
Summary
Getting a secret out of Vault requires proving who you are first. Vault supports pluggable auth methods β each one a different way for humans or machines to establish an identity and receive a token. This lesson covers the four you will actually use: human-friendly token auth and OIDC/LDAP for people, AppRole for servers and CI/CD pipelines, and Kubernetes auth for in-cluster workloads β including how to keep long-lived credentials out of your config files entirely.
π― What You'll Learn
- β Explain how auth methods map external credentials β Vault identity + policies
- β Manage token types, TTLs, and renewals instead of hoarding root tokens
- β Configure AppRole with RoleID/SecretID and use it from a script or CI job
- β Wire Vault to LDAP (or OIDC via Keycloak) for human single sign-on
- β Configure Kubernetes auth so pods authenticate with their service account β no stored secrets
- β Choose the right method per consumer type with a decision table
How Auth Methods Work
Every request to Vault needs a token. Auth methods are factories for tokens:
LDAP creds βββ
AppRole IDs ββΌβββΆ [Auth Method] βββΆ Vault Token βββΆ Policies attached βββΆ secret access
K8s JWT ββββββ€ (validates (identity + (least privilege,
User/pass ββββ creds) TTL) audit trail)
What Happens: you log in through a method (vault login -method=...), Vault validates the credential against the source system, builds a token with an identity and a set of policies, and hands it back. From then on, all requests carry X-Vault-Token. The token expires; re-authenticate when it does.
Enable any method under its mount point:
export VAULT_ADDR=http://127.0.0.1:8200 VAULT_TOKEN=hvs.root-token
docker exec -e VAULT_ADDR=$VAULT_ADDR -e VAULT_TOKEN=$VAULT_TOKEN vault \
vault auth list
Method 1: Tokens (Baseline)
Vault's native method is always enabled (token/). You already used one: the initial root token.
Create a Scoped Token Instead of Using Root
vault token create -policy=app-readonly -ttl=1h -renewable=true
Output:
token hvs.CAESIJ...
token_accessor aBcDeF...
token_duration 1h
token_renewable true
token_policies ["app-readonly" "default"]
What Happens: the new token can do only what app-readonly allows (policies defined in rbac-basics), dies after an hour unless renewed, and can be revoked instantly:
vault token revoke hvs.CAESIJ... # dead everywhere, immediately
vault token lookup hvs.CAESIJ... # inspect TTL, policies, meta
Token rules of thumb:
| Rule | Why |
|---|---|
| Never ship the root token into apps | Root bypasses ALL policy checks |
| Prefer short TTL + renewable | Limits blast radius of leaks |
| Revoke on role change/offboarding | Instant kill switch beats password rotation |
| Use batch tokens (stateless) for high-volume reads | No lease tracking overhead |
Method 2: AppRole (Machines, CI/CD)
The problem with static tokens for automation: if they leak, attackers get a working credential indefinitely. AppRole splits machine identity into two parts delivered through different channels:
- RoleID β like a username. Not secret by itself; identifies which app is asking.
- SecretID β like a password. Must be distributed securely.
Login requires both:
# Enable and define a role
vault auth enable approle
vault write auth/approle/role/ci-deployer \
token_policies="app-deploy" \
token_ttl=20m \
token_max_ttl=1h \
secret_id_ttl=24h \
secret_id_num_uses=10
# Fetch the two values
vault read -field=role_id auth/approle/role/ci-deployer/role-id
vault write -field=secret_id auth/approle/role/ci-deployer/secret-id
What Happens: this role's tokens live at most 1h, and each SecretID works max 10 times within 24h β a leaked SecretID has a hard expiry even if nobody notices.
Consumer script (works on any server or CI runner):
#!/usr/bin/env bash
set -euo pipefail
ROLE_ID="$APPROLE_ROLE_ID" # injected via CI variable / env file
SECRET_ID="$APPROLE_SECRET_ID" # injected as a protected secret
VAULT_TOKEN=$(curl -s --request POST \
--data "{\"role_id\":\"$ROLE_ID\",\"secret_id\":\"$SECRET_ID\"}" \
$VAULT_ADDR/v1/auth/approle/login | jq -r '.auth.client_token')
curl -s -H "X-Vault-Token: $VAULT_TOKEN" \
"$VAULT_ADDR/v1/secret/data/prod/api-key" | jq -r '.data.data.key'
What Happens: the runner authenticates once, gets a ~20-minute token scoped to app-deploy, fetches exactly the secret it needs, and the token evaporates after the job. Nothing long-lived ever sits in CI variables except the SecretID β which self-expires.
Hardening options per role: bind_secret_id=true (default), CIDR restrictions (token_bound_cidrs), and wrapping responses (one-time-use delivery):
vault write -wrap-ttl=5m -field=wrapping_token \
auth/approle/role/ci-deployer/secret-id
# deliver wrapping_token; consumer unwraps ONCE to reveal secret_id
Method 3: LDAP / OIDC (Humans)
Humans shouldn't manage individual Vault tokens. Point Vault at your central identity provider (identity-management covers standing up Keycloak/LDAP).
LDAP
vault auth enable ldap
vault write auth/ldap/config \
url="ldaps://keycloak.internal:636" \
userdn="ou=people,dc=fogserv,dc=cloud" \
groupdn="ou=groups,dc=fogserv,dc=cloud" \
binddn="cn=vault-reader,ou=service,dc=fogserv,dc=cloud" \
bindpass="$LDAP_BINDPW"
# Map AD/LDAP groups β Vault policies
vault write auth/ldap/groups/security-team policies=admin-audit
vault write auth/ldap/groups/developers policies=dev-secrets
vault write auth/ldap/groups/break-glass policies=root-manager
Then humans just run vault login -method=ldap username=jane β their group membership decides what they can touch. Offboard them in LDAP once and Vault access disappears automatically.
OIDC (Keycloak, Authentik, Googleβ¦)
vault auth enable oidc
vault write auth/oidc/config \
oidc_discovery_url="https://sso.fogserv.cloud/realms/fogserv" \
oidc_client_id="vault" \
oidc_client_secret="$OIDC_CLIENT_SECRET" \
default_role="user"
vault write auth/oidc/role/user \
bound_audiences="vault" \
allowed_redirect_uris="http://localhost:8250/oidc/callback" \
token_policies="default" \
oidc_scopes="openid,profile,groups"
vault login -method=oidc opens a browser, bounces through your SSO, done. Groupβpolicy mapping works the same way via auth/oidc/groups/*.
Method 4: Kubernetes Auth (In-Cluster Workloads)
The gold standard for pods: authenticate using the pod's own service account JWT, which Kubernetes issues and rotates automatically. No secrets stored anywhere.
On the Kubernetes side, create the reviewer binding:
kubectl create serviceaccount api-service -n prod
On the Vault side (run where kubectl context points at the cluster):
vault auth enable kubernetes
vault write auth/kubernetes/config \
kubernetes_host="https://k0s-api.internal:6443"
vault write auth/kubernetes/role/api-service \
bound_service_account_names="api-service" \
bound_service_account_namespaces="prod" \
token_policies="api-service-secrets" \
token_ttl=15m
What Happens: only pods running as SA api-service in namespace prod may exchange their JWT for a 15-minute Vault token scoped to api-service-secrets. Any other workload is rejected before touching secrets.
Pod consumption β the clean pattern is Vault Agent injecting secrets, but the direct version shows the mechanics:
apiVersion: v1
kind: Pod
metadata:
name: api-service
namespace: prod
spec:
serviceAccountName: api-service # β this IS the credential
containers:
- name: app
image: registry.internal/myapp:1.7.2
env:
- name: VAULT_ADDR
value: "https://vault.internal:8200"
- name: VAULT_SA_JWT # auto-mounted by Kubernetes
valueFrom:
fieldRef:
fieldPath: metadata.annotations['kubernetes.io/service-account/token']
command: ["/bin/sh","-c"]
args:
- |
VAULT_TOKEN=$(curl -s \
--data "{\"jwt\":\"$VAULT_SA_JWT\",\"role\":\"api-service\"}" \
$VAULT_ADDR/v1/auth/kubernetes/login | jq -r .auth.client_token)
export DB_PASS=$(curl -s -H "X-Vault-Token: $VAULT_TOKEN" \
$VAULT_ADDR/v1/secret/data/prod/db | jq -r .data.data.password)
exec ./serve
For production use Vault Agent Injector or the CSI driver so templates render secrets into files/env without app-side code β same auth flow underneath.
Choosing the Right Method
| Consumer | Method | Long-lived credential stored? | Revocation story |
|---|---|---|---|
| Admin/human interactive | OIDC or LDAP | None (SSO) | Disable in IdP |
| CI/CD pipeline | AppRole (+wrapping) | SecretID (short TTL, limited uses) | Delete SecretID / revoke accessor |
| Server outside K8s | AppRole | SecretID | Same |
| Pod in k0s/K8s | Kubernetes auth | None | Delete SA / policy binding |
| Break-glass emergency | Root token (offline) | Physical safe | N/A β used rarely |
Anti-patterns to avoid: shipping root tokens in .env; sharing one AppRole among unrelated services (you lose per-app blast-radius isolation); leaving secret_id_ttl=0 (never expires).
Troubleshooting & Common Issues
| Symptom | Likely cause | Fix |
|---|---|---|
permission denied right after successful login |
Login β authorization; token lacks policies | vault token lookup β check policies; add via role config |
AppRole login fails with invalid role or secret ID |
SecretID expired or use-count exhausted | Issue fresh SecretID; check secret_id_num_uses |
K8s auth: service account name not authorized |
Mismatched SA name/namespace in role | Compare exact strings in bound_service_account_* |
| LDAP login works, no permissions | Groups not mapped to policies | vault read auth/ldap/groups/<group> |
| OIDC redirect error | Callback URI not in allow-list | Add exact allowed_redirect_uris entry |
| Clock skew errors with JWT validation | Host time drift > few minutes | Run chrony/NTP everywhere (see ../basics/linux-fundamentals) |
Debug sequence that solves most auth mysteries:
vault token lookup <token> # who am I, what policies?
vault policy read <policy-name> # what CAN this identity do?
vault audit list # then read audit logs for the actual denials
π Related
- Previous: vault-introduction
- Next: vault-secrets β KV engines, dynamic database credentials
- rbac-basics β writing the policies these methods attach
- identity-management β standing up Keycloak/LDAP that feeds OIDC auth
- ../containers/k0s-introduction β cluster context for K8s auth
- ../cicd/secrets-management β AppRole in pipeline design
Change Log
- 2026-08-14 β Initial lesson created as part of KB course build-out (security Phase 4).