Skip to main content

Authenticate an embedded app without a template

Embedded Shopify apps get access tokens through token exchange: App Bridge provides a short-lived ID token proving the merchant's current session, and your backend exchanges it with Shopify for an access token to call Shopify APIs. For the endpoint and parameters behind this flow, see the access tokens reference.

When you scaffold your app with Shopify CLI, the Shopify app template handles this automatically. Use this tutorial if you're building an embedded app with a custom frontend that doesn't call authenticate.admin(), or if you need to understand what the template does under the hood.

Info

For server-side integrations acting on stores in your own Shopify organization, use the client credentials grant instead. To learn how authentication works for other common approaches, see About app authentication.

In this tutorial, you'll learn how to do the following tasks:

  • Get an ID token from App Bridge
  • Exchange it for an offline or online access token
  • Make authenticated GraphQL Admin API requests
  • Refresh an expiring offline token

Your backend language selection applies to the backend steps. The first step runs in the browser, so its samples are always JavaScript.

Requirements

App credentials

You have your app's client ID and client secret from the Dev Dashboard.

Access scopes

You've configured the access scopes your app needs.

Dev store

You've installed your app on a dev store, which grants the access scopes it requests.

App Bridge

Your frontend loads App Bridge from the CDN script tag, so the shopify global is available to call.

Project

Anchor to Get an ID token from App BridgeGet an ID token from App Bridge

An ID token is a short-lived JWT that Shopify generates when a merchant opens your app. It proves the request is from an authenticated Shopify user. App Bridge generates a fresh token for each session and your frontend sends it to your backend on every request.

Anchor to Call idToken() in your frontendCall idToken() in your frontend

The method you use depends on where your app is rendered:

  • In App Home, your app's main page in the Shopify admin, call shopify.idToken().
  • In an admin UI extension, which adds functionality to an admin resource page such as Products or Orders, call auth.idToken().

ID tokens expire after one minute. Fetch a fresh one on each request rather than caching it.

Anchor to Send the token to your backendSend the token to your backend

Include the ID token in the Authorization header on requests from your frontend to your backend.

Anchor to Exchange the ID token for an access tokenExchange the ID token for an access token

Your backend validates the ID token, then sends it to Shopify's token endpoint to receive an access token. Shopify supports two access token types. Offline access tokens are the default: they persist across sessions and aren't tied to a specific user, which suits background jobs, webhooks, and scheduled work. Online access tokens are tied to the staff member who opened your app and expire with their session, so use them when your app needs to enforce per-user permissions or attribute actions to a specific person. For a fuller comparison, see Access token types.

Anchor to Validate the ID tokenValidate the ID token

Validate the ID token before your app trusts it. The samples use a JWT library for this: jsonwebtoken for Node.js and PyJWT for Python. Check the signature against your client secret, then verify the following claims:

ClaimWhat to check
expMust be in the future.
nbfMust be in the past.
audMust match your app's client ID.
iss and destHostnames must match.

If any check fails, the library raises an error and the exchange routes return a 401 before reaching Shopify's token endpoint.

When you reject a request with a 401, set the X-Shopify-Retry-Invalid-Session-Request header if the request came from your frontend over XHR or fetch. App Bridge intercepts the response, fetches a fresh ID token, and retries the request once.

Anchor to Request an offline access tokenRequest an offline access token

Offline access tokens persist across sessions. Use them for background jobs, webhooks, and anything that runs without an active merchant session. New public apps must use expiring offline access tokens. Include expiring=1 in your request and Shopify returns a refresh token alongside your access token. If you're updating an existing public app that still requests non-expiring tokens, see Migrate to expiring offline access tokens.

If the ID token is expired or otherwise invalid, Shopify returns a 400 Bad Request. An ID token lives about a minute, so this is a routine condition rather than a server fault, and a fresh ID token fixes it. Handle it the way you handle a failed validation check: respond with a 401 and the X-Shopify-Retry-Invalid-Session-Request header, so App Bridge fetches a new ID token and retries. Reserve 5xx responses for failures that a fresh ID token can't fix, such as rejected client credentials.

Anchor to Handle the offline token responseHandle the offline token response

Store the access_token and refresh_token securely on your backend, along with expires_in and refresh_token_expires_in so you know when each one expires. Never expose them to the browser.

Response fields
FieldDescription
access_tokenThe token to include in API requests.
scopeThe access scopes granted to your app. The exchange doesn't ask for scopes, so this is a readback of what you configured for your app. To add or change them, see Manage access scopes.
expires_inSeconds until the token expires.
refresh_tokenUsed to get a new access token before expiry.
refresh_token_expires_inSeconds until the refresh token expires (90 days).

Anchor to (Optional) Request an online access token(Optional) Request an online access token

Online access tokens are scoped to the current user and session. Use them if your app needs to enforce per-user permissions or attribute actions to a specific staff member. Skip this step if offline access tokens meet your needs. For more about online tokens, including their lifetime and the associated_user fields, see Online access tokens.

Anchor to Exchange the ID token for an online tokenExchange the ID token for an online token

Each online token is valid for 24 hours and tied to the staff member who opened your app. As with the offline exchange, a 400 Bad Request means the ID token is stale, so answer it with a 401 and the retry header.

Anchor to Handle the online token responseHandle the online token response

The associated_user_scope field contains the intersection of your app's scopes and that user's permissions. Use it to enforce what the current staff member can do.

Response fields
FieldDescription
access_tokenThe token to include in API requests. Valid for 24 hours.
scopeThe access scopes granted to your app.
expires_inSeconds until the token expires.
associated_user_scopeThe intersection of your app's scopes and the user's permissions.
associated_userThe authenticated user. Only trust email if email_verified is true.

Anchor to Make authenticated API requestsMake authenticated API requests

Include the access token in the X-Shopify-Access-Token header on all GraphQL Admin API requests.

Anchor to Add the access token headerAdd the access token header

Include the access token in every request. If the access token expires or Shopify rejects it during an active merchant session, re-run token exchange with the ID token from the current request. For background jobs with no active session, use the refresh_token instead. See Refresh an expiring offline token.

Anchor to Refresh an expiring offline tokenRefresh an expiring offline token

Use this flow when your access token expires and no merchant session is available, such as for webhooks, scheduled jobs, and other background work. Without an active session, you can't get a fresh ID token from App Bridge, so you use the stored refresh_token instead.

Store the refresh_token from the exchange response and use it before expires_in seconds elapse. Refresh tokens are one-time-use and expire after 90 days.

Background refreshes usually run in-process from your scheduler or job runner, so there's no endpoint to secure. If you do expose one over HTTP, protect it with your own internal credential, such as a shared secret.

Anchor to Send the refresh requestSend the refresh request

POST https://{shop}.myshopify.com/admin/oauth/access_token
Parameters for refreshing an access token
ParameterDescription
client_idrequiredThe client ID for the app.
client_secretrequiredThe client secret for the app.
grant_typerequiredThe value refresh_token indicates that a refresh token grant is being used.
refresh_tokenrequiredThe refresh token received when the access token was issued.

Shopify returns a new access token and a new refresh token. A few things to keep in mind:

  • Each refresh issues a new refresh token with a new 90-day expiration. Store it and discard the old one.
  • The previous access token stays valid until its expires_in duration ends, but use the new token for all new requests.
  • Token refreshes are resilient to ambiguous failures. If a refresh request times out, hits a network error, or returns a transient 5xx, retry promptly with the same refresh_token. The repeated request returns the same rotated credentials rather than issuing another set. Treat a successful rotation as consuming the previous refresh token, and don't rely on a fixed retry-window duration.

When a refresh token can no longer be used, Shopify returns 401 Unauthorized with {"error": "invalid_request"} and the description This request requires an active refresh_token. Shopify returns this same response for every terminal case, including an unknown token, a token replayed after the retry window, an expired token, and a revoked or uninstalled app, so don't branch on which one it was.

Treat that 401 as final: stop retrying, and re-authenticate by running token exchange the next time a merchant opens your app. Transient failures, such as network errors, timeouts, 5xx responses, and 429 responses, are safe to retry with the same refresh_token.

Refreshing an access token doesn't change your client secret. Rotating your client secret is a separate operation that invalidates tokens tied to the old secret and requires migrating every store. See Rotate your client secret.

Was this page helpful?