Cannot get refresh token from Authorization Code Flow with PKCE

I’m working on a web application that needs to get refresh tokens using the PKCE flow. For reasons outside this ticket, I need to roll my own client for the Auth0 API, so I’m not using the JS SDKs.

I am attempting to follow the flow here: Add Login Using the Authorization Code Flow with PKCE

  • My API has Enable Offline Access enabled
  • My Application’s Grant Types include Authorization Code
  • I’m successfully authorizing the user through the /authorize endpoint, including offline_access in the scope.
  • I get the code after authorization, and I can successfully exchange the code and code_verifier for tokens using grant_type: authorization_code

But no matter what I try, I only get back access_token and id_token, not refresh_token, in the /oauth/token response payload. The response payload even includes offline_access in its scopes, but no refresh token is forthcoming.

What am I missing here? I’ve been banging my head against this for a day or more, and have read most of the Auth0 documentation that appears relevant.

Sample code for the /authorization call:

// Make the URL
const params = new URLSearchParams();
params.append('response_type', 'code');
params.append('response_mode', 'query');
params.append('audience', audience);
params.append('state', state);
params.append('code_challenge', codeChallenge);
params.append('code_challenge_method', 'S256');
params.append('client_id', clientId);
params.append('redirect_uri', redirectURI);
params.append('scope', scope);

const url = `https://${domain}/authorize?${params}`;

// Redirect the current page
window.location.assign(url);

Sample code for the /oauth/token call:

const params = new URLSearchParams();
params.append('grant_type', 'authorization_code');
params.append('audience', audience);
params.append('client_id', clientId);
params.append('code_verifier', transaction.code_verifier);
params.append('code', code);
params.append('redirect_uri', redirectURI);

const res = await request(`https://${domain}/oauth/token`, {
  method: 'POST',
  body: params.toString(),
  headers: {'Content-Type': 'application/x-www-form-urlencoded'}
});

Update

I finally found the root cause in the Auth0 logs:

Type: Warning During Login
Description: The scope ‘offline_access’ was requested, but no ‘refresh_token’ was issued because the authorization code exchange originated from a browser

Still not sure how to get around this, as I’d like to acquire a refresh token from a browser interface.

I’ve confirmed that I can get the refresh token through the Auth0 debugger. That suggests that there’s a problem in the way I’m implementing the flow, not in the Auth0 setup. Would appreciate any suggestions.

I believe this is what you are needing.

1 Like

Thanks for sharing that with the rest of community!

Using refresh token rotation for now. I was hoping to acquire a long-lived refresh token for customers to use in their own applications, but for now the Auth0 advice is to use client secrets for this use case.

1 Like