Skip to content
CommerceGeek home

Securing your app, together: Shopify's security checklist and the 1 January 2027 token deadline

Shopify asks every app developer to act this week on security steps, including AI scans and moving to expiring offline access tokens, which public apps need by 1 January 2027.

A magnifying glass over a padlock beside a ticked clipboard checklist and a calendar page.
Published
21 Oct 2026
Affects
GraphQL Admin API

What changed

On 20 October 2026, Shopify published a changelog post asking every app developer to act on a set of security steps "this week". It says attacks are increasing across the app ecosystem, in line with the rest of the industry, as AI gets better at finding vulnerabilities.

The post names three new or newly highlighted tools:

  • A security check for AI Toolkit. A single prompt checks for Shopify-specific vulnerabilities, such as unauthenticated routes, cross-shop data access and unverified webhooks. Shopify says most partners in early access found something they did not know about.

  • Expiring access tokens. Shopify says short-lived tokens limit the damage if a token leaks, and that they will soon be required for all public apps.

  • Better log filtering. The Dev Dashboard logs can be filtered by client IP to look at traffic patterns and find suspicious sources.

Who is affected

Shopify is asking every app developer to act on the security steps in the announcement. The request is not limited to public apps or to any one API.

The expiring offline access token requirement is narrower. It applies to public apps that make GraphQL Admin API requests. Shopify says it does not apply to custom apps or apps created by merchants, and it is enforced on GraphQL Admin API requests only.

The migration page says you need it only if your public app still requests non-expiring offline access tokens for the GraphQL Admin API. Newly scaffolded apps already request expiring tokens through the expiringOfflineAccessTokens flag.

Shopify also says it is working on further platform changes:

  • tightening the boundaries for storefront app code

  • adding IP address restrictions so your keys only work from your servers

  • raising App Store security requirements

What to do: the security checklist

Shopify asks every app developer to work through the following steps this week. The announcement is dated 20 October 2026. That is when it was published, not a deadline for any single step.

  • Run the AI Toolkit security check. A single prompt checks for Shopify-specific vulnerabilities such as unauthenticated routes, cross-shop data access and unverified webhooks. Shopify says most partners in early access found something they did not know about. The best practices page notes that the check reads your app's source code and does not test the app while it runs, so a clean result is not proof that the app is secure. Use it alongside your existing tools and reviews, and run it on every release.

  • Use vulnerability scanners to confirm that every endpoint of your app is secured.

  • Treat all rendered data as untrusted, whether it comes from metafields or from your own servers. Escape and sanitise inputs. Shopify's best practices page points out that Liquid does not escape output for you, so use escape in HTML, json inside a <script> tag and url_encode in URL parameters. Do not execute a store-supplied string as code, and prefer textContent over innerHTML.

  • Check your Dev Dashboard logs for suspicious API requests. Filter by client IP to look at traffic patterns and find sources you do not recognise.

  • Turn on two-step authentication for your Partner account, hosting, CDN, code repositories and email. Remove access for anyone who no longer needs it.

  • Keep your emergency developer email in the Partner Dashboard current, and make sure it reaches someone who can act quickly.

  • Review and apply the security best practices in Shopify Dev Docs (https://shopify.dev/docs/apps/build/security/following-security-best-practices).

Moving to expiring access tokens is also on Shopify's list. It is covered in its own section.

Migrating to expiring offline access tokens

This applies to public apps that request non-expiring offline access tokens for the GraphQL Admin API. Newly scaffolded apps already request expiring tokens. Custom apps and apps created by merchants are not affected. The steps depend on how your app requests tokens today.

Start requesting expiring tokens

If your app uses @shopify/shopify-app-remix or @shopify/shopify-app-react-router, add the expiringOfflineAccessTokens flag to your shopifyApp() configuration and redeploy with shopify app deploy. The template then issues, stores and refreshes expiring tokens for you.

Before

const shopify = shopifyApp({
  // ...
});

After

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

If your app implements token exchange or the OAuth authorization code grant itself, add expiring=1 to the request that obtains the access token. Store the access_token and refresh_token from the response, along with expires_in and refresh_token_expires_in. Use the refresh token before expires_in seconds elapse, then store the new refresh token that comes back and use that one next time.

Before

-d 'client_id={client_id}' \
-d 'client_secret={client_secret}' \
-d 'code={authorization_code}'

After

-d 'client_id={client_id}' \
-d 'client_secret={client_secret}' \
-d 'code={authorization_code}' \
-d 'expiring=1'

Migrate existing installations directly

Existing installations migrate when your app acquires and stores an expiring token pair. If you do not want to wait for users to open your app, use the direct migration flow. It also covers background-only apps such as webhook consumers, app proxy backends and Flow actions. It uses the stored non-expiring token and your client credentials, and needs no ID token or user session.

curl -X POST \
  https://{shop}.myshopify.com/admin/oauth/access_token \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  -d 'grant_type=urn:ietf:params:oauth:grant-type:token-exchange' \
  -d 'client_id={client_id}' \
  -d 'client_secret={client_secret}' \
  -d 'subject_token={non_expiring_offline_token}' \
  -d 'subject_token_type=urn:shopify:params:oauth:token-type:offline-access-token' \
  -d 'requested_token_type=urn:shopify:params:oauth:token-type:offline-access-token' \
  -d 'expiring=1'
Unverified example.

The subject token must be an active non-expiring offline token belonging to your app and store. Shopify rejects online tokens, delegate tokens, scope-restricted tokens and tokens that already expire. Both requested_token_type and expiring=1 are required. Otherwise Shopify returns 400 Bad Request with invalid_subject_token or invalid_requested_token_type.

Recover a lost response

If you lose the response, repeat the same request with the original non-expiring token and valid client credentials within seven days of the initial exchange. Eligible retries return the same token pair. Shopify extends the access token's expiry when needed but not the refresh token's. Recovery stops once your app refreshes the issued pair or a later token acquisition for the same store retires it. If a retry returns invalid_subject_token, acquire a new token through ID-token exchange or the authorization code grant.

Handle 403 and 401 errors

  • A rejected non-expiring token returns 403 Forbidden. The errors value contains Non-expiring access tokens are no longer accepted for the Admin API, followed by a migration link, so match on that phrase. A missing access scope also returns 403, so check the message before re-authenticating.

  • App template apps: authenticate.admin(request) handles re-authentication automatically.

  • Direct token exchange: catch the 403 and re-run token exchange.

  • Authorization code grant: catch the 403, clear the stored token and send the merchant through OAuth again.

  • Refresh token errors: an expired, already replaced or otherwise invalid refresh token returns 401 Unauthorized with {"error": "invalid_request"}. Treat it as a signal to re-authenticate; the merchant must reopen your app in the Shopify admin to trigger a new token request.

Deadline

The token deadline is a calendar date that applies to every public app, not a date tied to an API version. The announcement on 20 October 2026 is not the deadline. Shopify asks developers to act that week, but the enforcement date is later.

1 January 2027: non-expiring offline tokens

From 1 January 2027, Shopify rejects any GraphQL Admin API request from a public app that presents a non-expiring offline access token. Merchants lose access to your app until they re-authenticate. New public apps already cannot use non-expiring tokens.

1 March 2027: script tags stop running

The security best practices page says script tags are deprecated and will stop running on storefronts on 1 March 2027. Shopify's recommended replacements are theme app extensions, or a web pixel if the script only collects analytics or conversion data. The announcement does not mention this date, so check whether your app still relies on script tags.

Side note: API version 2027-01

The best practices page also says that from GraphQL Admin API version 2027-01, an app can downgrade an optional write scope to read. This is a new capability, not a removal, and it does not break anything on upgrade.

No dates yet for other changes

Shopify has not announced dates for storefront app code boundaries, IP address restrictions on keys or the higher App Store security requirements. It says it will share details and timelines before any of them takes effect.

Reviewing logs in the Dev Dashboard

The Dev Dashboard **Logs** view puts Admin API requests, webhook deliveries, Function runs, UI extension errors and your own app events on one timeline. Shopify's checklist asks you to use it to look for suspicious activity, and its best practices page says a leaked access token used to make requests shows up as API traffic your app did not send.

Filters worth using

  • **Type**: choose the event type, such as GraphQL request or REST request. Selecting a type unlocks the filters specific to it.

  • **Shop**: limit entries to one merchant's shop.

  • **Status** and **Status code**: separate successful requests (202) from failed ones (4xx, 5xx).

  • **Client IP** and **User agent**: available for GraphQL request and REST request types. Use them to find requests that come from outside your own infrastructure.

Limits to know about

  • Logs are retained for 30 days, and a single time range can span at most 7 days. To look further back within the 30 days, check one range at a time.

  • On a wide time range the table can show a sample rather than every match. The dashboard says when this happens, so narrow the filters before concluding that an event did not occur.

  • A GraphQL request can return 200 and still fail on the operation, so open the entry and read the response.

Alerts

The dashboard also raises alerts for non-expiring offline access tokens. A **Deprecated offline token** alert is a Warning while a token is approaching its deadline and Critical once it has passed. Alerts appear in the **Alerts** column of the Apps list, in the banner on the app's Overview and in the Portfolio summary on Home.

Get the weekly digest

One email every Monday: what changed in Shopify, the deadlines coming up and what’s been announced, plus the week’s new guides. Free, and you can unsubscribe in one click.

Sources

  1. Securing your app, together · Shopify · accessed 20 Oct 2026
  2. Migrate to expiring offline access tokens · Shopify developer docs · accessed 20 Oct 2026
  3. Monitor app performance · Shopify developer docs · accessed 20 Oct 2026
  4. Following security best practices · Shopify developer docs · accessed 20 Oct 2026

AI-assisted, reviewed by Scott. Every claim is checked against the sources above.