---
title: Authentication for apps built with Shopify CLI
description: >-
  How an app scaffolded with Shopify CLI authenticates with the GraphQL Admin
  API, and what you configure as you build.
source_url:
  html: >-
    https://shopify.dev/docs/apps/build/authentication-authorization/cli-app-authentication
  md: >-
    https://shopify.dev/docs/apps/build/authentication-authorization/cli-app-authentication.md
---

# Authentication for apps built with Shopify CLI

This page is for apps [scaffolded with Shopify CLI](https://shopify.dev/docs/apps/build/cli-for-apps). A scaffolded app authenticates with the Shopify admin automatically. Installation and scope grants happen through Shopify managed installation, and the [app template](https://shopify.dev/docs/api/libraries-and-templates#app-templates) 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](https://shopify.dev/docs/apps/build/authentication-authorization/implement-token-exchange). To learn how authentication works for other common approaches, see [About app authentication](https://shopify.dev/docs/apps/build/authentication-authorization).

***

## How it works

Your scaffolded app authenticates through [token exchange](https://shopify.dev/docs/apps/build/authentication-authorization/access-tokens#token-exchange-grant):

1. The merchant installs your app and approves the [access scopes](https://shopify.dev/docs/api/usage/access-scopes) it requests.
2. Shopify issues a short-lived [ID token](https://shopify.dev/docs/apps/build/authentication-authorization/id-tokens) that proves the request comes from an authenticated Shopify user.
3. Your app exchanges the ID token for an [access token](https://shopify.dev/docs/apps/build/authentication-authorization/access-tokens).
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.](https://shopify.dev/assets/assets/images/apps/auth/token-exchange-flow-BiwtKfdW.png)

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

***

## Customize 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](https://shopify.dev/docs/api/usage/access-scopes), and cross-reference the [GraphQL Admin API](https://shopify.dev/docs/api/admin-graphql) docs for the specific fields and mutations your app uses.

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

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

Then deploy:

```bash
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.

***

## Authenticate 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.

```typescript
// 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();
}
```

***

## Customize 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.

### Offline 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](https://shopify.dev/docs/apps/build/webhooks/get-started) 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`:

```javascript
// 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](https://shopify.dev/docs/apps/build/authentication-authorization/migrate-to-expiring-offline-access-tokens).

For exact token lifetimes, see [Token lifetimes](https://shopify.dev/docs/apps/build/authentication-authorization/access-tokens#token-lifetimes). For how Shopify refreshes and retires offline tokens, see [Offline access tokens](https://shopify.dev/docs/apps/build/authentication-authorization/access-tokens#offline-access-tokens).

### Online 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:

```javascript
// 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`.

```typescript
// 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](https://shopify.dev/docs/apps/build/authentication-authorization/access-tokens#token-lifetimes). For the `associated_user` fields, see [Online access tokens](https://shopify.dev/docs/apps/build/authentication-authorization/access-tokens#online-access-tokens).

***

## Migrate 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](https://shopify.dev/docs/apps/build/cli-for-apps/manage-app-config-files#link-and-configure-apps).
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](#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](https://shopify.dev/docs/apps/build/authentication-authorization/implement-token-exchange) to configure token exchange.

***

## Next steps

* Learn how to [build your app's main UI](https://shopify.dev/docs/apps/build/app-home) in App Home.
* Learn how to [connect UI extensions to your app's backend](https://shopify.dev/docs/apps/build/admin/actions-blocks/connect-app-backend), which wraps route responses in the `cors` helper.
* Learn how to [query and mutate store data](https://shopify.dev/docs/apps/build/graphql) using the GraphQL Admin API.
* Learn how to [manage access scopes](https://shopify.dev/docs/apps/build/authentication-authorization/manage-access-scopes) after launch, including optional scopes you request only when a merchant uses the feature that needs them.
* Learn how to [set up webhooks](https://shopify.dev/docs/apps/build/webhooks) for background tasks that use your offline access token.

***
