Configure token and session expiry
Build on Kinde
Refresh tokens are used to request new access tokens.
Access tokens are issued when a user makes an authentication request or a call is made to an API. An access token gives permission to enter and interact with a system.
Access tokens usually have an intentionally short lifetime. However, rather than having a user need to re-authenticate frequently, a refresh token can be used to request a new access token. Refresh tokens operate without user intervention, extending session access without the same security risk as requesting a new access token.
There is no cap on the number of times a refresh token can be used — it remains valid until it expires or is rotated out.
To get a refresh token, you need to include the offline scope when you initiate an authentication request through the https://<your_subdomain>.kinde.com/oauth2/auth endpoint. You also need to initiate Offline Access in your API.
Kinde uses offline (not offline_access). The OIDC specification defines this scope as offline_access, but Kinde does not support it. If you use offline_access, the auth flow will complete successfully but no refresh token will be returned, and no error will be shown.
Details on how to do this is provided in our SDKs, but here’s how to do it yourself.
Below is an example using the Authorization Code grant, with the offline scope being one of the scopes passed to the authentication request.
curl -G "https://<your_subdomain>.kinde.com/oauth2/auth" \ -d "response_type=code" \ -d "client_id=<client_id>" \ -d "redirect_uri=<redirect_uri>" \ -d "scope=offline%20email%20openid%20profile"In a real app you typically redirect the user to this URL in a browser rather than calling it with curl. The response will redirect the user to your redirect_uri with an authorization code in the query string; you then exchange that code for tokens (including a refresh token) via POST to https://<your_subdomain>.kinde.com/oauth2/token with grant_type=authorization_code.
Refresh tokens are stored in sessions. When a session needs to be refreshed (for example, a pre-defined time has passed), the app uses the refresh token on the backend to obtain a new access token, using the https://<your_subdomain>.kinde.com/oauth2/token endpoint with grant_type=refresh_token.
Example: request a new access token using a refresh token
curl -X POST "https://<your_subdomain>.kinde.com/oauth2/token" \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "grant_type=refresh_token" \ -d "client_id=<client_id>" \ -d "client_secret=<client_secret>" \ -d "refresh_token=<refresh_token>"Use this only from a secure backend. Do not send client_secret from frontend or public clients; use PKCE for SPAs and similar apps.
Example of a refresh token response
{ "access_token": "<access_token>", "refresh_token": "<refresh_token>", "token_type": "Bearer"}Kinde always rotates refresh tokens. When you use an existing refresh token to request a new access token, a new refresh token is also generated and provided with your new access token. The old refresh token becomes immediately invalid.
Kinde allows a small overlap period after rotation where both the previous and new refresh token are valid. This is to account for retries and bad network connections (for example, parallel requests that both send the old token before the rotation response arrives). You can set the lifetime of a refresh token in Kinde. It needs to be longer than the life of an access token.
When multiple applications share the same custom domain, they normally share a single refresh_token cookie. That can cause one app’s refresh token to overwrite another’s—for example, signing in to App B may replace the refresh token that App A had set, breaking App A’s session.
To avoid this, Kinde supports client-specific refresh token cookies. When enabled for an application, the refresh token is stored in a cookie named with the application’s client ID prefix (for example, refresh_token_abc123), so each app keeps its own refresh token independently.
refresh_token_abc123).refresh_token cookie.The refresh_token cookie uses Path=/oauth2/token by default. This path scope means the cookie is only sent by the browser on requests to that specific path — not to your application routes. If you are debugging cookie behavior, look for the cookie under the /oauth2/token path in your browser’s DevTools (Application > Cookies), not at the root path.
You should store the refresh token you get with your initial /token request. Otherwise, your user will need to go through the sign in process again, to get a new access token.
getToken function to silently refresh tokensFront-end packages do not all behave the same way:
JavaScript (PKCE) SDK: getToken() uses the cached access token when it is still considered active; otherwise it attempts a silent refresh via the refresh token before resolving. On failure it returns undefined (it does not throw). See When does the JavaScript SDK refresh access tokens in the background? and Does getToken throw or return an error object when refresh fails?.
React SDK: Use getAccessToken() on the hook. It only reads the cached JWT from session storage and does not refresh it. Silent refresh runs on provider init, on a pre-expiry timer (about 10 seconds before access token expiry by default), and optionally when refreshOnFocus is enabled; when those succeed, the next getAccessToken() reads the updated value. See Does getAccessToken throw or return an error object when something goes wrong? and When does the React SDK refresh tokens silently?.
Refresh tokens have a default lifetime of 15 days (1,296,000 seconds). You can change this in Settings > Environment > Applications > [your app] > Tokens. The refresh token lifetime must always be set longer than the access token lifetime.
There is no cap on the number of times a refresh token can be used within its lifetime — it remains valid until it expires or is rotated out.
For a comparison of all token default lifetimes, see Configure token and session expiry.
Token security can be approached in a number of ways. We recommend at least covering the basics of: