I am fetching a refresh token for a user from Auth0, but when I use the refresh token to refresh the ID and access token, I am getting this error.
{
"error": "invalid_grant",
"error_description": "Unknown or invalid refresh token."
}
Token exchange request:
const data = new URLSearchParams({
grant_type: 'client_credentials',
code: dto.code,
client_id: process.env.OAUTH_CLIENT_ID || '',
redirect_uri: dto.redirect_uri,
scope: 'openid profile email offline_access',
});
Refresh token request:
const data = new URLSearchParams({
grant_type: 'refresh_token',
refresh_token: dto.refresh_token,
client_id: process.env.OAUTH_CLIENT_ID || '',
client_secret: process.env.OAUTH_CLIENT_SECRET || '',
scope: 'openid profile email offline_access',
});
Hi @yahyashareef,
The client_credentials
grant type is designed for machine-to-machine communication and does not issue refresh tokens. To get a refresh token for a user, you must use a grant type that involves user authentication, such as the authorization_code
grant.
Have a good one,
Vlad
I am using the authentication_code but still having the same error
const data = new URLSearchParams({
grant_type: 'authorization_code',
code: dto.code,
client_id: process.env.OAUTH_CLIENT_ID || '',
redirect_uri: dto.redirect_uri,
scope: 'openid profile email offline_access',
});
Still returning Unknown or invalid refresh token.
Hi again @yahyashareef,
The error Unknown or invalid refresh token
indicates an issue with the refresh token itself or the configuration of your application and API in Auth0. It commonly occurs if offline access isn’t enabled for your API, if you’re re-using a rotated token, or if the token wasn’t issued correctly in the first place.
authorization_code
is the right grant for user-centric flows. The error you’re seeing now happens during the second step: when your application tries to exchange the refresh token for a new access token.
This points to a few very likely causes:
-
Offline Access Not Enabled: Your API (the “Audience” for the token) must be explicitly configured in the Auth0 Dashboard to allow the issuance of refresh tokens.
-
Refresh Token Rotation: If token rotation is enabled on your application, each time you use a refresh token, it is invalidated and a new one is issued. If your code attempts to use the old token again, it will fail with this exact error.
-
Missing Client Secret (for Confidential Apps): If your application is a “Regular Web Application,” it’s considered a confidential client and must provide its client_secret
during the initial authorization_code
exchange. Your code snippet for that exchange is missing it, which could mean a valid refresh token was never issued.
I recommend you generate a completely new authorization code and token set after checking the following settings.
1. Enable “Allow Offline Access” for Your API
This is the most frequent cause of this issue.
- Go to your Auth0 Dashboard.
- In the left sidebar, navigate to Applications > APIs.
- Select the API you are using as the audience for your tokens.
- Click the Settings tab.
- Scroll down to the “Access Settings” section and enable the “Allow Offline Access” toggle.
- Click Save.
2. Handle Refresh Token Rotation
Check if rotation is active and ensure your code handles it correctly.
- In the dashboard, navigate to Applications > Applications.
- Select the application you are using.
- Under the Settings tab, scroll down to Refresh Token Behavior.
- If Rotation is enabled, you must capture and save the new refresh token that is returned in the body of a successful refresh token exchange. The old one is now invalid.
3. Add the Client Secret to the Token Exchange
If your application is a Regular Web App, the client_secret
is required to prove its identity when exchanging the code for tokens.
Your authorization_code
grant request should look like this:
const data = new URLSearchParams({
grant_type: 'authorization_code',
code: dto.code,
client_id: process.env.OAUTH_CLIENT_ID || '',
client_secret: process.env.OAUTH_CLIENT_SECRET || '', // <-- Ensure this is included
redirect_uri: dto.redirect_uri,
});
The scope
parameter is generally not needed in the /oauth/token
request, as the scopes were already determined in the initial /authorize
call that generated the code
.
If you have any further questions feel free to reach out!
Have a good one,
Vlad