Customer Account API: no refresh_token returned when using Shopify app client ID

We’re implementing Customer Account API authentication for our Shopify app using the app-level client_id configured through [customer_authentication] in shopify.app.toml.

The authorization-code + PKCE flow works correctly. We receive a valid Customer Account API access token and ID token, and Customer Account API requests work as expected.

However, the token response does not contain a refresh_token.

Our authorization request uses:

response_type=code
client_id=<Shopify app client ID>
scope=openid email customer-account-api:full
code_challenge=<PKCE challenge>
code_challenge_method=S256
redirect_uri=<registered customer_authentication redirect URI>

We then exchange the authorization code at the discovered token_endpoint with:

grant_type=authorization_code
client_id=<same Shopify app client ID>
redirect_uri=<same redirect URI>
code=<authorization code>
code_verifier=<PKCE verifier>

The raw response from Shopify contains:

{
  "access_token": "...",
  "token_type": "bearer",
  "expires_in": 3600,
  "id_token": "..."
}

There is no refresh_token.

The access token itself contains the expected scope:

openid email customer-account-api:full

We have reproduced this with two separate Shopify apps, so it doesn’t appear to be specific to one app configuration.

Headless Customer Account client comparison

We also tested the same flow using the Customer Account API client ID from a Headless storefront on the same development shop.

With that client ID, the token response does contain a refresh_token.

So the observed difference is:

Shopify app client ID ([customer_authentication])
→ access_token
→ id_token
→ expires_in: 3600
→ NO refresh_token

Headless Customer Account API client ID
→ access_token
→ refresh_token
→ id_token
→ expires_in: 3600

This is particularly problematic for a native mobile app. With the Shopify app client ID, after the one-hour access token expires we have no refresh token available to obtain a new access token and therefore have to send the customer through the authorization flow again.

Reauthorization is relatively painless when the Shopify browser session still exists, but it still requires opening the authorization UI again instead of silently refreshing the Customer Account session.

The Customer Account API documentation appears to indicate that the authorization-code flow should return a refresh token and documents using it to refresh an expired access token.

Is the absence of refresh_token when using the Shopify app client ID with [customer_authentication] expected behavior?

If it is expected, is there another supported way to obtain a refresh token/persistent Customer Account session when using the app-level client ID?

Or is this a bug in the app-level Customer Account authentication flow?

Hey @appyshop - thanks for reaching out. I found an older thread reporting what seems to be the same difference between Headless and app client IDs: Customer Auth has no refresh_token , so wondering if this is potentially related.

This does look a bit odd, so I’d like to dig into it further on our end. Could you share:

  • The x-request-id and UTC timestamp for one app-client exchange and one Headless-client exchange
  • The development shop domain and affected app ID

Happy to open a DM if you’d rather not share those publicly. I’ll loop back once I have more info.

Thanks! Yes, that older thread looks like the same behavior we’re seeing. I’ll reproduce both exchanges again and capture the x-request-id and UTC timestamp for each. A DM would be appreciated for sharing the development shop domain and app details.

Hi @Alan_G,

Same issue here: app [customer_authentication] client gets an access_token but no refresh_token. Could you open a DM? I’ll send the x-request-id, timestamp, shop, and app ID.

Thank you!

Hey @Angelina_Prokopeva and @appyshop - apologies for the delay on this, I’ll send you both DMs here and look into this further on our end!