Skip to main content

Set up session tokens with App Bridge 2.0

Caution

This page covers App Bridge 2.0. App Bridge 2.0 calls the short-lived JWT a session token; the current App Bridge calls the same concept an ID token. See About app authentication for the modern approach. If you're building a new app, the React Router template handles all of this for you. This content is maintained for teams maintaining existing App Bridge 2.0 apps.

App Bridge 2.0 is the npm-based predecessor to the current script-tag App Bridge. Unlike the current version, it doesn't handle session tokens automatically. In App Bridge 2.0, embedded apps retrieve session tokens manually using the getSessionToken helper and attach them to backend requests using authenticatedFetch. The backend then decodes and verifies the token before exchanging it for an API access token via token exchange.

We recommend using the Shopify App gem or Shopify Node API library to decode and verify tokens rather than implementing verification manually.


The getSessionToken helper retrieves a session token from Shopify. It dispatches the APP::SESSION_TOKEN_REQUEST action and resolves when App Bridge responds with APP::SESSION_TOKEN_RESPOND.

import createApp from "@shopify/app-bridge";
import { getSessionToken } from "@shopify/app-bridge/utilities";

const app = createApp({
apiKey: "12345",
host: new URLSearchParams(location.search).get("host"),
});

const sessionToken = await getSessionToken(app);

getSessionToken returns a Promise that resolves with the session token or rejects with APP::ERROR::FAILED_AUTHENTICATION if the token is undefined.


Anchor to Authenticate requestsAuthenticate requests

The authenticatedFetch helper attaches the session token as a Bearer authorization header on requests to your backend.

import { authenticatedFetch } from "@shopify/app-bridge/utilities";

const authFetch = authenticatedFetch(app);
const response = await authFetch("/api/products");

To add custom headers or caching logic, pass a custom fetch wrapper as the second argument. The wrapper receives the request URI and merged options including the Authorization header. Make sure your wrapper passes all options through:

import deepMerge from "@shopify/app-bridge/actions/merge";

const yourCustomFetchWrapper = (uri, options) => {
const aggregateOptions = deepMerge(options, {
method: "POST",
headers: { "Content-Type": "application/json" },
});
return fetch(uri, aggregateOptions);
};

const shopifyFetch = authenticatedFetch(app, yourCustomFetchWrapper);

Anchor to Decode and verify session tokens on the serverDecode and verify session tokens on the server

A session token is a JWT with the structure <header>.<payload>.<signature>, all base64url-encoded. Session tokens are signed with HS256 using your app's client secret.

Anchor to Verify the payload claimsVerify the payload claims

Extract and verify the following fields from the payload:

FieldVerification
expMust be in the future
nbfMust be in the past
iss, destHostnames must match. dest is the shop origin (myshop.myshopify.com).
audMust match your app's client ID
subUser ID of the authenticated user

Discard the token and return an error if any check fails.

Note

Without third-party cookies, CSRF tokens in cookies aren't always possible. The session token serves as an alternative. You can trust it was issued by Shopify to your app's frontend.

Anchor to Verify the signatureVerify the signature

  1. Take the <header>.<payload> portion of the token.
  2. Hash it with SHA-256 and sign using HS256 with your app's client secret as the signing key.
  3. Base64url-encode the result.
  4. Compare it to the <signature> portion of the token.

Use JWT.io as a reference for decoding tokens during development.


Once you've verified the session token, exchange it for an API access token using token exchange. Store the access token and use it on subsequent GraphQL Admin API requests with the X-Shopify-Access-Token header.

If your app doesn't have a valid offline or online access token, redirect to a bounce page that loads the App Bridge script before redirecting back to the requested path. This ensures the session token is available in the Authorization header on the follow-up request.

Don't embed protected data in the unauthenticated HTML you return before verification. Expose only the shop domain there, and serve protected data from an authenticated API route once the token is verified.

For online access token expiry, request a new token via token exchange when the existing token expires.


Was this page helpful?