Auth0 tokens don’t just expire; they’re designed to be short-lived and rotated for security, which is a feature, not a bug.

Let’s see this in action. Imagine a user logs in. Auth0 issues an ID token and an access token. The ID token, a JWT, proves the user’s identity and has a short id_token_lifetime, typically 1 hour. The access token, also a JWT, grants access to resources and has an access_token_lifetime, also usually 1 hour.

{
  "iss": "https://your-tenant.auth0.com/",
  "sub": "auth0|abcdef1234567890",
  "aud": "your-api-identifier",
  "exp": 1678886400, // This is the expiration timestamp
  "iat": 1678882800, // This is the issued at timestamp
  "azp": "your-client-id",
  "scope": "openid profile email",
  "nonce": "some_random_string"
}

The exp field is critical here. When this timestamp passes, the token is no longer valid. But we don’t want to make users re-authenticate every hour. This is where refresh tokens come in.

When a user logs in, Auth0 can issue a refresh token. This is a long-lived, opaque token (meaning it’s not a JWT and its contents aren’t directly interpretable by the client) that the client uses to request new ID and access tokens without the user having to re-enter their credentials. The default lifetime for a refresh token is 30 days, but this is configurable.

Here’s how the flow works:

  1. User Login: User authenticates with Auth0. Auth0 issues ID, access, and refresh tokens.
  2. API Call: Client uses the access token to call a protected API.
  3. Token Expiration: The access token expires. The API rejects the request.
  4. Token Refresh: The client uses the refresh token to request new ID and access tokens from Auth0’s /oauth/token endpoint.
  5. New Tokens Issued: Auth0 validates the refresh token and issues fresh ID and access tokens. The client can then use the new access token to call the API.

The "rotation" aspect is key. When a refresh token is used to obtain new ID and access tokens, Auth0 can also invalidate the old refresh token and issue a new one. This is a security measure: if a refresh token is compromised, it can only be used once to get new tokens, and then it’s gone. This behavior is controlled by the refresh_token_rotation setting in your Auth0 tenant.

You configure token lifetimes and rotation in the Auth0 dashboard under Applications > APIs > [Your API] > Settings or Applications > Applications > [Your App] > Settings.

For Access Token Lifetime and ID Token Lifetime, you’ll find these settings. The default is 1 hour (3600 seconds). You can set this to a different value, for example, 2 hours (7200 seconds):

  • Access Token Lifetime: 7200
  • ID Token Lifetime: 7200

For Refresh Token Lifetime, this is often found under Security > Token. The default is 30 days. You can set it to, say, 90 days (7776000 seconds):

  • Refresh Token Lifetime: 7776000

The refresh_token_rotation setting can be none (no rotation, the same refresh token is re-issued), rotate (the used refresh token is invalidated, and a new one is issued), or rotate-sliding (similar to rotate, but the lifetime of the new refresh token is extended). The default is rotate.

When you set refresh_token_rotation to rotate, and a client successfully uses a refresh token to get new tokens, Auth0 invalidates the original refresh token. If that same original refresh token is used again, Auth0 will reject it, forcing the user to re-authenticate. This prevents an attacker who steals an old refresh token from continuously obtaining new access tokens.

The most surprising thing is that refresh tokens, despite their name, don’t just "refresh" the existing tokens; they are the key to obtaining entirely new sets of tokens, including a new refresh token if rotation is enabled.

This mechanism is how you balance security (short-lived access tokens) with user experience (not forcing constant re-authentication). The next step is understanding how to revoke tokens programmatically when a user logs out or their permissions change.

Want structured learning?

Take the full Auth0 course →