Navigation
credential-types
OAuth Tokens
OAuth tokens in Vault
OAuth 2.0 tokens have two lifetimes: the access token’s native expiry (set by the authorization server) and the Vault lease TTL. Vault tracks both independently. This means you can distinguish between “the lease expired” (the agent’s access window closed) and “the token expired” (the token itself needs to be refreshed).
Storing an OAuth token
await vault.credentials.create({
name: 'github-oauth-prod',
type: 'oauth-token',
value: 'gho_abc123...',
metadata: {
service: 'github',
scopes: 'repo,read:org',
refreshToken: 'ghr_refresh_xyz...',
tokenExpiresAt: '2026-05-01T00:00:00Z',
environment: 'production',
},
policy: {
defaultLeaseTTL: '1h',
maxLeaseTTL: '8h',
},
});
The access token goes in the value field. The refresh token, scopes, and token expiry go in metadata. Vault encrypts all metadata alongside the value, so the refresh token is protected by the same encryption envelope.
Token expiry vs. lease expiry
These are two separate clocks:
- Token expiry is when the OAuth access token becomes invalid at the authorization server. After this point, API calls using the token will return 401 errors regardless of Vault’s state.
- Lease expiry is when Vault stops granting access to the token value. After this point, the agent cannot retrieve the token from Vault, but the token itself might still be valid at the authorization server.
Good practice is to set the lease TTL shorter than the token’s remaining lifetime. If the token expires in 2 hours, a 1-hour lease TTL gives the agent access while ensuring the lease closes before the token goes stale.
Refresh workflows
When an access token expires, you can refresh it and update the credential in Vault:
// Fetch the current credential to get the refresh token
const cred = await vault.credentials.get({ name: 'github-oauth-prod' });
const refreshToken = cred.metadata.refreshToken;
// Call the authorization server's token endpoint
const newTokens = await refreshOAuthToken(refreshToken);
// Update the credential in Vault
await vault.credentials.update({
name: 'github-oauth-prod',
value: newTokens.accessToken,
metadata: {
...cred.metadata,
refreshToken: newTokens.refreshToken ?? refreshToken,
tokenExpiresAt: newTokens.expiresAt,
},
});
This is a manual refresh pattern. For services that support it, you can automate this in a scheduled job that checks token expiry and refreshes proactively.
Validation
Vault validates that the oauth-token value is a non-empty string. It does not validate the token with the authorization server, because doing so would require network calls to external services during storage, which would make the write path unreliable.
If the tokenExpiresAt metadata field is present, Vault parses it as an ISO 8601 timestamp and includes it in credential listings, so you can see at a glance which tokens are approaching expiry.
Metadata conventions
| Field | Description | Example |
|---|---|---|
service | The OAuth provider | github |
scopes | Comma-separated OAuth scopes | repo,read:org |
refreshToken | The refresh token, if the grant type supports it | ghr_refresh_xyz... |
tokenExpiresAt | When the access token expires at the provider | 2026-05-01T00:00:00Z |
grantType | The OAuth grant type used | authorization_code |
environment | Production, staging, or development | production |
Scoping leases for OAuth tokens
Because OAuth tokens often carry broad permissions (a token with repo scope can access all repositories), consider using strict lease TTLs and agent attribution to limit exposure. A 15-minute lease to an agent that needs to push to one repository is far safer than a permanent checkout of a token with organization-wide access.