StorefrontAPI: Modifying access scope does not grant the scope

I’m having issues updating a Customer phone number via the Storefront API.

Error: “Access denied for customerUpdate field. Required access: unauthenticated_write_customers access scope. Also: Requires valid customer access token.”

However the token used does have that scope and I am providing the accessToken as well. If I remove the unauthenticated_read_customersscope from this token then I can no longer query (proving I am using and changing scope for the correct access token).

Why would the scope change on the token not take effect?

mutation customerUpdate {
  customerUpdate(
    customer: { phone: "12345678901" }
    customerAccessToken: "secret"
  ) {
    customerAccessToken {
      accessToken
    }
    customer {
      email
      phone
    }
  }
}

Hey @bbeecher! Can you share a bit more about your setup? Specifically whether you’re using the Headless channel, a custom app, or generating tokens via the Admin API?

If you’re creating it programmatically via storefrontAccessTokenCreate, the token inherits your app’s unauthenticated scopes at creation time. Adding unauthenticated_write_customers to your app after the token was already created won’t update existing tokens, you’d need to create a new one.

If you’re using the Headless channel instead, permission changes should apply across all storefronts without regenerating tokens.

@Donal-Shopify It is a Headless channel token being used. When I removed and added the read scope on the token it took effect immediately, but adding the above write scope does not take effect ever (assuming the error is accurate evidence of that).
It may be worth noting this was originally a Legacy Custom App with Storefront API access, and that app is still alive and has the desired scopes assigned. This Legacy app’s token isn’t the one I am using but it does have the same issue.

Thanks for the context Bryan - sounds like you’ve covered the basics. Would you be able to share an x-request-id for a failed customerUpdate mutation?

I’ll be able to use it to check our logs and the Headless channel’s config at the time of the request and see where we might be going wrong

Thanks for your persistence here! x-request-id: e71db729-3b0b-4335-b81b-2242d4b688dc-1770658022

Hey Bryan, I pulled up the logs for that request and have some good news (sort of). The request shows your Headless channel (api_client_id 12875497473) with unauthenticated_write_customers included in the API permission scopes that were evaluated. So the scope change you made did actually take effect.

The customerUpdate mutation has two separate requirements: your storefront access token needs the unauthenticated_write_customers scope, and you need to pass a valid customerAccessToken that identifies the customer.

I was able to reproduce the exact same error on a test store that has the scope enabled by passing an invalid customer access token. The error lists both requirements regardless of which one actually failed, so it looks like a scope problem when the real issue is the customer access token not passing validation.

How are you generating the customerAccessToken you’re passing into the mutation?

If you’re creating it via customerAccessTokenCreate with email and password, check that it hasn’t expired (the expiresAt field will tell you). If the store has been moved to new customer accounts, that flow won’t work since there are no passwords to authenticate with. In that case you’d want to use the Customer Account API’s customerUpdate directly instead of the Storefront API version.

Well that is helpful in narrowing the issue down. It sounds like the token is incorrect.
I am using the access_token from the session obtained in signing in via customer account api. this begins with shcat_.

Ah, gotcha. The shcat_ token you’re getting from the Customer Account API is a different token type from what the Storefront API’s customerUpdate expects.

The Storefront API needs a customerAccessToken created via customerAccessTokenCreate, which authenticates with email and password. Since you’re signing in through the Customer Account API (new customer accounts), there are no passwords in play, so that flow isn’t available to you.

The Customer Account API does have its own customerUpdate mutation, but its input only supports firstName and lastName right now, not phone.

The practical workaround is to handle this server-side. Use the Customer Account API session to identify the customer, then make an Admin API call to update their phone via customerUpdate on the backend. That mutation supports the full set of customer fields including phone.

In practice, your backend would use the Customer Account API session to resolve the logged-in customer’s ID, then pass that ID to the Admin API mutation so the admin token never leaves the server.

This makes sense to me. I didn’t realize the token would be invalid in that API (and it sounds like I cannot obtain one on behalf of the customer).
Thank you for the detailed explanation, Donal!

1 Like