Vault AppRole in Practice: One Worked Configuration and the Mistakes That Undermine It
AppRole swaps long-lived Vault tokens for a role ID and secret ID pair. Here is a complete role setup, the checks that prove it works, and where teams usually get it wrong.
20 Aug 2025, 11:07 UTC

Machines that need secrets from Vault face the same bootstrapping problem as humans: they need a credential to fetch credentials. The lazy fix is a long-lived token pasted into an environment file, which survives redeploys, leaks through container images, and never expires when it should. Vault's AppRole auth method is the built-in alternative: the application presents a role ID and a secret ID, and Vault returns a token whose lifetime and permissions are capped by the role definition. The takeaway up front: AppRole's security lives almost entirely in two decisions — how the secret ID reaches the application, and how narrow the role's policy is. Get those right and the rest is mechanics; get either wrong and the short-lived token is cosmetic.
How the two-credential login works
AppRole splits machine identity into two parts. The role_id identifies which role the application is logging in as — it functions like a username and is not, by itself, a secret. The secret_id is the credential, closer to a short-lived, optionally single-use password. The application sends both to the auth/approle/login endpoint and receives a client token.
That token carries the policies attached to the role, starts with the role's token_ttl, and can be renewed only until token_max_ttl — after which the application must log in again with a fresh secret ID. The ceiling is enforced server-side, so even a compromised client cannot renew its way past it.
A worked configuration
Run these on an operator workstation or CI bootstrap job with VAULT_ADDR set and a Vault token that can manage auth methods (typically an admin or sudo-capable token). The syntax assumes Vault 1.12 or newer; on servers older than 1.2 the role parameters were named policies, ttl, and max_ttl, so check vault version first.
vault auth enable approle
vault write auth/approle/role/webapp \
token_policies="web-app-policy" \
token_ttl="1h" \
token_max_ttl="2h" \
secret_id_ttl="24h" \
secret_id_num_uses=10
Then fetch the credentials:
vault read -format=json auth/approle/role/webapp/role-id
vault write -f auth/approle/role/webapp/secret-id
The first command returns the role_id; the second returns a secret_id in its data field. Treat the secret ID like a password: it appears in command output, so capture it straight into your delivery channel rather than leaving it in terminal history or a shell script.
The application itself logs in from its own host or CI job — that step needs only network access to Vault plus the two values, no admin token:
vault write auth/approle/login \
role_id="<role-id>" \
secret_id="<secret-id>"
Substitute the values returned by the previous two commands. The response includes a client_token, its policies, and the lease duration; the app then uses that token as VAULT_TOKEN for reads on paths the web-app-policy allows.
Two parameters in the role deserve attention because their defaults are permissive: secret_id_ttl defaults to no expiry and secret_id_num_uses defaults to unlimited (0). The example tightens both — the secret ID dies after 24 hours or 10 logins, forcing periodic re-issue instead of a credential that quietly works forever.
Verifying the setup
vault auth list— the output should include anapprole/entry.vault read auth/approle/role/webapp— confirmtoken_ttl,token_max_ttl,token_policies,secret_id_ttl, andsecret_id_num_usesmatch what you intended.- Log in once as the application would, then
vault token lookup <token>— the TTL shown should not exceed the role'stoken_max_ttl, and the policy list should show exactlyweb-app-policy(plusdefault, which Vault attaches to every token unless suppressed withtoken_no_default_policy=true). vault token capabilities <token> secret/data/webapp/config— confirms the token can read the paths the app actually needs, which by implication checks that the policy is not broader than that.
If an audit device is enabled, AppRole login attempts appear there — that is where you will notice repeated failures from something probing with a wrong or exhausted secret ID.
Where AppRole setups go wrong
- Shipping the secret ID in source or images. If the secret ID is baked into a container image or a committed config file, anyone who can pull either can authenticate. Use a delivery channel instead: response wrapping (request the secret ID with
-wrap-ttl, so it arrives as a single-use wrapped token the application unwraps exactly once), injection by the CI system, or a rotated bootstrap secret. Wrapping is the mechanism Vault provides for precisely this handoff. - Broad policies. A role pointing at a catch-all policy makes the one-hour TTL meaningless — an attacker holding the pair gets everything, repeatedly. Scope
web-app-policyto specific paths such assecret/data/webapp/*and nothing else. - Single-use secret IDs with no re-issue path.
secret_id_num_uses=1is good hygiene, but if the application cannot obtain a new secret ID when it re-authenticates attoken_max_ttl, it locks itself out mid-flight. Decide the re-issue mechanism before tightening the use count. - Misjudging the role ID. The
role_idalone cannot mint a token, so it is not a credential — but a leaked role ID paired with a leaked secret ID is exactly a login. Avoid emitting both into the same logs or config dump. - Using AppRole where a platform auth method fits. On Kubernetes, the kubernetes auth method binds logins to service accounts; on AWS, Azure, and GCP, the cloud methods prove identity from the platform itself. AppRole fits VMs, bare metal, and CI runners where you control a channel for the secret ID. Choosing it elsewhere means owning the delivery problem the platform method would have solved for you.
Two optional tightenings where your environment supports them: bound_cidr_list restricts logins to known network ranges, useful when the application's source addresses are stable, and token_no_default_policy=true strips the default policy if its capabilities are wider than the role needs.
Changing your mind
Enabling the method and creating roles changes server state. vault auth disable approle removes the mount and invalidates tokens issued through it, so treat that command as an incident-response action rather than routine cleanup. Removing a single role with vault delete auth/approle/role/webapp blocks future logins for that role while already-issued tokens expire on their own TTL.
The practical check that an AppRole setup is done: a login succeeds from the application's host, the resulting token's capabilities match a narrowly scoped policy and nothing more, and the secret ID expires on a schedule you chose. If any of those three fails, fix it before replicating the pattern across more services.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.