Migrating a Shopify Ruby on Rails App to Expiring Offline Access Tokens
Migrate your Shopify Ruby on Rails app to expiring offline access tokens. Step-by-step technical guide covering database migrations, model storage updates, context setup, and background task management.
Shopify Apps
10 min

With Shopify requiring all public apps to transition to expiring offline access tokens by January 1, 2027, developers must update their application architectures. In the Rails ecosystem, the official shopify_app and shopify_api gems provide the necessary helpers to handle this flow.
This guide walks through the database migrations, configuration updates, and background worker logic needed to adopt token rotation using standard Shopify tools.
Token Lifetime and Expiration Rules :
When implementing token rotation, keep these strict token lifetimes in mind:
Access Token (1 Hour): Once generated, the access token is short-lived, with a standard lifespan of 1 hour. The gem automatically handles refreshing it before it expires.
Refresh Token (90 Days of Inactivity): The refresh token operates on a sliding window of 90 days. Each successful token refresh resets this 90-day timer. However, if a merchant does not open or interact with your app for 90 consecutive days and no background API tasks are executed to trigger a refresh, the refresh token will expire, requiring complete re-authentication.
Step 1: Update the Gems
To access built-in support for token rotation, ensure your application is running shopify_app version 23.0. (or higher) and shopify_api version 16.0 (or higher).
Update your Gemfile:
Run bundle update to apply the updates.
Step 2: Database Migration
Your session storage model (typically the Shop or Session model) needs new columns to hold the access token expiration timestamp, the refresh token, and the refresh token's expiration timestamp.
Run the official generator to add the necessary columns:
(Using --skip ensures the generator only creates the database migration for missing fields instead of regenerating the model file.)
Alternatively, generate a migration manually:
Run rails db:migrate to update your schema.
Step 3: Update the Shop Model Concern
To support token rotation automatically, you must switch your model to use the modern ShopifyApp::ShopSessionStorage concern. This concern replaces deprecated storage methods.
Modify app/models/shop.rb:
The standard ShopSessionStorage concern dynamically detects the presence of the refresh_token and expires_at database columns and automatically handles the token refresh flow.
Step 4: Configure the Shopify API Context
You must configure the shopify_api gem context to request and handle expiring tokens during new installs and session lookups.
Update your ShopifyAPI::Context.setup configuration (usually located in config/initializers/shopify_app.rb):
Step 5: Migrating Existing Non-Expiring Tokens
For shops that already have your app installed, you need to perform a one-time exchange to trade their existing permanent access token for an expiring/refresh token pair.
The Exchange Helper
You can perform the exchange programmatically using standard SDK methods:
This returns a ShopifyAPI::Auth::Session object containing the new access_token, expires_at, and refresh_token.
Migration Script Pattern
You can write a Rake task or database migration script to loop through existing shops and exchange their tokens:
Step 6: Background Jobs & API Requests
When executing API requests in background workers (e.g., Sidekiq/ActiveJob) or controller actions, wrap your calls in the standard session helper.
Automatic Token Refreshing
Under the hood, with_shopify_session will load the session credentials, verify if the access_token is expired or near expiration, and automatically use the stored refresh_token to retrieve a new pair. It then saves the new rotated tokens to the database before executing your API block.
Stale Token Prevention in Job Arguments
Do not pass raw access tokens as arguments to your background queues (e.g., perform_async(token)). Since tokens expire and rotate, background jobs must always fetch the latest credentials dynamically from the database by passing the shop's ID (e.g., perform_async(shop_id)).
Step 7: Handling Re-Authentication
If a token refresh fails for instance, if the refresh token itself has expired due to 90 days of inactivity the API will raise a ShopifyAPI::Errors::RefreshTokenExpiredError.
Here is how you handle re-authentication across different scopes:
A. In Web Requests (Controllers)
When using standard shopify_app controllers (e.g., controllers inheriting from ShopifyApp::AuthenticatedController or including ShopifyApp::EnsureInstalled), the gem automatically catches authorization errors and handles redirects to the login/OAuth flow. No manual code is required.
B. In Background Jobs
Because background workers run out-of-band without a browser session, they cannot redirect the user. You must catch the error, log it, and flag the shop in your database as needing re-authentication (e.g., setting a needs_reauth flag or clearing the invalid token).
When the merchant next opens the Shopify Admin dashboard, your embedded frontend will detect the missing/invalid session and guide them through standard OAuth re-authentication to retrieve a new set of tokens.
Official Documentation Links
For further details and updates on the token rotation APIs:
