Skip to main content

Authentication for apps built with Shopify CLI

This page is for apps scaffolded with Shopify CLI. A scaffolded app authenticates with the Shopify admin automatically. Installation and scope grants happen through Shopify managed installation, and the app template exchanges and refreshes access tokens, so you never implement OAuth directly. The following sections explain how that works, and what you customize as your app grows.

Info

If you're building an embedded app with a custom frontend that doesn't use an app template, see Get access tokens without a template. To learn how authentication works for other common approaches, see About app authentication.


Your scaffolded app authenticates through token exchange:

  1. The merchant installs your app and approves the access scopes it requests.
  2. Shopify issues a short-lived ID token that proves the request comes from an authenticated Shopify user.
  3. Your app exchanges the ID token for an access token.
  4. Your app includes the access token in the X-Shopify-Access-Token header on every GraphQL Admin API request.
  5. When the access token expires, the app template refreshes it without interrupting the merchant.

The app template performs steps 2 through 5 for you, so you never handle tokens directly. The access token stays on your backend and is never exposed to the browser.

Sequence diagram of token exchange between the merchant, App Bridge, your app's backend, and Shopify.

You don't have to implement token exchange yourself, but you can customize the access scopes your app requests, add the authenticate call to new routes that need store data, and choose whether your app uses offline or online access tokens.


Anchor to Customize your access scopesCustomize your access scopes

The template ships with a starter set of access scopes in shopify.app.toml. Tailor them to match the data your app reads and writes: add the scopes for the resources your app uses, and remove the ones it doesn't.

To know which scopes your app needs, check the Access scopes reference, and cross-reference the GraphQL Admin API docs for the specific fields and mutations your app uses.

Declare your scopes in your app's shopify.app.toml:

# shopify.app.toml
[access_scopes]
scopes = "read_products,write_orders"

Then deploy:

shopify app deploy

Merchants approve these scopes when they install your app. If you add scopes later, merchants who already have your app installed approve the new ones the next time they open it.


Anchor to Authenticate requests in your routesAuthenticate requests in your routes

Your app authenticates each request by calling authenticate.admin(request). It validates the request's ID token, performs token exchange, and returns an authenticated admin client for the GraphQL Admin API. The template already includes this call in the routes it generates, so a freshly installed app can query the GraphQL Admin API right away. Add the same call to any new loader or action you write that needs it.

// app/routes/app._index.tsx
import { authenticate } from "../shopify.server";
import type { LoaderFunctionArgs } from "react-router";

export async function loader({ request }: LoaderFunctionArgs) {
const { admin } = await authenticate.admin(request);

const response = await admin.graphql(`
query {
shop {
name
}
}
`);

return await response.json();
}

Anchor to Customize your access tokensCustomize your access tokens

Your app can use two kinds of access tokens: offline tokens, which it uses by default, and online tokens, which you enable when you need them.

Anchor to Offline access tokensOffline access tokens

Offline access tokens persist across user sessions. Because they survive beyond a merchant's active session, they're especially useful for background jobs, webhooks, and scheduled tasks. When a webhook fires, your app retrieves the offline token stored during installation and uses it to make authenticated API calls. See Create a webhook subscription to configure delivery.

Expiring offline access tokens are required for public apps. They're short-lived, and the app template refreshes them automatically. A newly scaffolded app already has them enabled through the future flag in shopify.server.ts:

// shopify.server.ts
const shopify = shopifyApp({
// ...
future: {
expiringOfflineAccessTokens: true,
},
});

If you're migrating an existing app that doesn't have this flag, see Migrate to expiring offline access tokens.

For exact token lifetimes, see Token lifetimes. For how Shopify refreshes and retires offline tokens, see Offline access tokens.

Anchor to Online access tokensOnline access tokens

Online access tokens are tied to the staff member who opened your app, and their lifespan is limited to that user's session. If your app needs to enforce per-user permissions or attribute actions to a specific staff member, enable them alongside your offline tokens, and the app template manages both.

Set useOnlineTokens: true in your shopifyApp() configuration:

// shopify.server.ts
const shopify = shopifyApp({
// ...
useOnlineTokens: true,
});

When online access tokens are enabled, authenticate.admin(request) returns a session object scoped to the current user. The session.onlineAccessInfo field contains the authenticated user's details and associated_user_scope, which is the intersection of your app's scopes and that user's permissions. If a staff member lacks a scope your app has, it won't appear in associated_user_scope.

// app/routes/app.orders.tsx
export async function loader({ request }: LoaderFunctionArgs) {
const { admin, session } = await authenticate.admin(request);
const { associated_user, associated_user_scope } = session.onlineAccessInfo;

if (!associated_user_scope.includes("write_orders")) {
throw new Response("Forbidden", { status: 403 });
}

const response = await admin.graphql(`
query {
shop {
name
}
}
`);
const { data } = await response.json();

return { shopName: data.shop.name, staffId: associated_user.id };
}

The app template refreshes online tokens before they expire, but your app should still handle a 401 Unauthorized (expired token) or 403 Forbidden (the user lacks a required permission) gracefully. For exact token lifetimes, see Token lifetimes. For the associated_user fields, see Online access tokens.


Anchor to Migrate from legacy OAuthMigrate from legacy OAuth

If your app currently implements the authorization code grant flow manually (redirecting merchants through /admin/oauth/authorize), you can migrate to managed installation:

  1. If your app isn't set up with Shopify CLI yet, create or link a configuration file.
  2. Declare your scopes in shopify.app.toml.
  3. Run shopify app deploy.

For a worked example of the scope declaration and deploy, see Customize your access scopes.

After migration, Shopify handles installation and scope updates, and your app no longer needs OAuth callback routes for these operations. If you're not using an app template, see Get access tokens without a template to configure token exchange.



Was this page helpful?