Documentation Index
Fetch the complete documentation index at: https://mintlify.com/Shopify/shopify-app-js/llms.txt
Use this file to discover all available pages before exploring further.
This guide covers OAuth authentication flows for Shopify apps using the Shopify API library.
OAuth Flow
The library provides two main authentication methods:
- OAuth flow - For public apps
- Token exchange - For embedded apps (recommended)
Standard OAuth Flow
The OAuth flow involves two steps: beginning the OAuth process and handling the callback.
Begin OAuth
Redirect the user to Shopify’s OAuth authorization page.app.get('/auth', async (req, res) => {
await shopify.auth.begin({
shop: req.query.shop,
callbackPath: '/auth/callback',
isOnline: false,
rawRequest: req,
rawResponse: res,
});
});
Parameters:
shop - The shop domain (e.g., example.myshopify.com)
callbackPath - Where Shopify redirects after authorization
isOnline - true for online tokens, false for offline tokens
The begin function creates a state cookie and redirects to Shopify’s OAuth authorization endpoint.Source: lib/auth/oauth/oauth.ts:60-127 Handle OAuth Callback
Process the callback from Shopify and create a session.app.get('/auth/callback', async (req, res) => {
try {
const {session, headers} = await shopify.auth.callback({
rawRequest: req,
rawResponse: res,
});
// Store the session
await shopify.config.sessionStorage.storeSession(session);
// Redirect to app
res.redirect(`/?shop=${session.shop}`);
} catch (error) {
console.error('Auth error:', error);
res.status(500).send('Authentication failed');
}
});
The callback validates the HMAC, verifies the state cookie, and exchanges the authorization code for an access token.Source: lib/auth/oauth/oauth.ts:129-236
Token Exchange (Recommended for Embedded Apps)
Token exchange allows you to obtain access tokens using session tokens without redirecting users through OAuth.
import {RequestedTokenType} from '@shopify/shopify-api';
app.post('/api/auth/token-exchange', async (req, res) => {
const sessionToken = req.headers['authorization']?.replace('Bearer ', '');
const {session} = await shopify.auth.tokenExchange({
shop: req.body.shop,
sessionToken,
requestedTokenType: RequestedTokenType.OfflineAccessToken,
});
await shopify.config.sessionStorage.storeSession(session);
res.json({success: true});
});
Token Types:
RequestedTokenType.OnlineAccessToken - User-specific access (expires)
RequestedTokenType.OfflineAccessToken - Shop-level access (long-lived)
Source: lib/auth/oauth/token-exchange.ts:32-79
Online vs Offline Access Tokens
Offline Tokens
Online Tokens
Offline access tokens are tied to the shop and persist until uninstalled.await shopify.auth.begin({
shop: 'example.myshopify.com',
callbackPath: '/auth/callback',
isOnline: false, // Offline token
rawRequest: req,
rawResponse: res,
});
Use offline tokens for:
- Background jobs
- Webhooks
- Operations that don’t require a specific user
Online access tokens are user-specific and have an expiration date.await shopify.auth.begin({
shop: 'example.myshopify.com',
callbackPath: '/auth/callback',
isOnline: true, // Online token
rawRequest: req,
rawResponse: res,
});
Online tokens include onlineAccessInfo with user details:interface OnlineAccessInfo {
associated_user: {
id: number;
first_name: string;
last_name: string;
email: string;
account_owner: boolean;
locale: string;
collaborator: boolean;
};
}
Source: lib/session/session.ts:186-188
Session Management
After authentication, the library creates a Session object containing the access token.
interface Session {
id: string; // Unique session identifier
shop: string; // Shop domain
state: string; // OAuth state
isOnline: boolean; // Token type
scope?: string; // Granted scopes
accessToken?: string; // Access token
expires?: Date; // Token expiration
onlineAccessInfo?: OnlineAccessInfo; // User info (online tokens only)
}
Validating Sessions
const session = await shopify.config.sessionStorage.loadSession(sessionId);
if (!session.isActive(shopify.config.scopes)) {
// Session is expired or scopes have changed - re-authenticate
return res.redirect('/auth');
}
Source: lib/session/session.ts:198-206
Error Handling
The library throws specific errors during authentication:
import {
BotActivityDetected,
CookieNotFound,
InvalidOAuthError,
} from '@shopify/shopify-api';
try {
const {session} = await shopify.auth.callback({...});
} catch (error) {
if (error instanceof BotActivityDetected) {
// Bot detected, return 410 Gone
return res.status(410).send('Bot activity detected');
}
if (error instanceof CookieNotFound) {
// OAuth state cookie missing
return res.redirect('/auth');
}
if (error instanceof InvalidOAuthError) {
// Invalid callback parameters or HMAC
return res.status(400).send('Invalid OAuth callback');
}
}
Source: lib/error.ts:94-96
The OAuth flow includes bot detection using the isbot library. Bot requests receive a 410 Gone status.Source: lib/auth/oauth/oauth.ts:82-86
HMAC Validation
All OAuth callbacks are automatically validated using HMAC signatures:
// The library automatically validates:
// 1. HMAC signature from Shopify
// 2. State parameter matches cookie
// 3. Shop domain is valid
Source: lib/auth/oauth/oauth.ts:238-251
Complete Example
import express from 'express';
import {shopifyApi, RequestedTokenType} from '@shopify/shopify-api';
const shopify = shopifyApi({...});
const app = express();
// Begin OAuth
app.get('/auth', async (req, res) => {
await shopify.auth.begin({
shop: req.query.shop,
callbackPath: '/auth/callback',
isOnline: false,
rawRequest: req,
rawResponse: res,
});
});
// OAuth callback
app.get('/auth/callback', async (req, res) => {
const {session} = await shopify.auth.callback({
rawRequest: req,
rawResponse: res,
});
await shopify.config.sessionStorage.storeSession(session);
res.redirect(`/?shop=${session.shop}`);
});
Never use OAuth authentication for private/custom apps. Private apps should use the adminApiAccessToken configuration instead.Source: lib/auth/oauth/oauth.ts:253-260