Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.launchmystore.io/llms.txt

Use this file to discover all available pages before exploring further.

Session Tokens

Session tokens are JWTs (JSON Web Tokens) that authenticate your app’s API calls. They prove the request comes from a legitimate merchant session within the LaunchMyStore admin.

How Session Tokens Work

Getting a Session Token

Using App Bridge

import { createApp } from '@launchmystore/app-bridge';

const app = createApp({
  apiKey: 'your-client-id',
  host: new URLSearchParams(location.search).get('host')
});

// Get token (cached and auto-refreshed)
const token = await app.getSessionToken();

// Use in API calls
const response = await fetch('https://your-app.com/api/data', {
  headers: {
    'Authorization': `Bearer ${token}`
  }
});

Using React Hooks

import { useSessionToken } from '@launchmystore/app-bridge-react';

function MyComponent() {
  const { token, loading, error, refresh } = useSessionToken();
  
  const fetchData = async () => {
    const response = await fetch('/api/data', {
      headers: {
        'Authorization': `Bearer ${token}`
      }
    });
    return response.json();
  };
  
  if (loading) return <Loading />;
  if (error) return <Error message={error.message} />;
  
  return <button onClick={fetchData}>Fetch</button>;
}

Token Structure

Session tokens are JWTs with the following structure:
// Header
{
  "alg": "HS256",
  "typ": "JWT"
}

// Payload
{
  "iss": "launchmystore",           // Issuer
  "aud": "your-client-id",          // Your app's client ID
  "sub": "merchant-shop-id",        // Merchant's shop ID
  "dest": "https://store.launchmystore.io",  // Shop domain
  "exp": 1234567890,                // Expiration timestamp
  "iat": 1234567800,                // Issued at timestamp
  "nbf": 1234567800,                // Not valid before
  "jti": "unique-token-id",         // Unique token identifier
  "sid": "session-id"               // Session ID
}

// Signature
// HMACSHA256(header + "." + payload, clientSecret)

Token Lifecycle

Expiration

Session tokens expire after 1 hour. App Bridge automatically:
  1. Caches the current token
  2. Refreshes before expiration
  3. Returns cached token if still valid

Manual Refresh

Force a token refresh when needed:
// App Bridge
const freshToken = await app.getSessionToken({ forceRefresh: true });

// React hook
const { refresh } = useSessionToken();
refresh();

Verifying Tokens

Your backend should verify session tokens before processing requests.

Node.js Verification

import jwt from 'jsonwebtoken';

async function verifySessionToken(token, clientSecret, clientId) {
  try {
    const decoded = jwt.verify(token, clientSecret, {
      algorithms: ['HS256'],
      audience: clientId,
      issuer: 'launchmystore'
    });
    
    // Token is valid
    return {
      valid: true,
      shopId: decoded.sub,
      shopDomain: decoded.dest,
      sessionId: decoded.sid
    };
  } catch (error) {
    return {
      valid: false,
      error: error.message
    };
  }
}

// Express middleware
function requireSessionToken(req, res, next) {
  const authHeader = req.headers.authorization;
  
  if (!authHeader || !authHeader.startsWith('Bearer ')) {
    return res.status(401).json({ error: 'Missing authorization header' });
  }
  
  const token = authHeader.substring(7);
  const result = verifySessionToken(
    token,
    process.env.APP_CLIENT_SECRET,
    process.env.APP_CLIENT_ID
  );
  
  if (!result.valid) {
    return res.status(401).json({ error: 'Invalid token' });
  }
  
  req.shop = {
    id: result.shopId,
    domain: result.shopDomain
  };
  
  next();
}

// Use middleware
app.get('/api/data', requireSessionToken, (req, res) => {
  const { shopId, domain } = req.shop;
  // Process request for this shop
});

Python Verification

import jwt
from functools import wraps
from flask import request, jsonify

def verify_session_token(token, client_secret, client_id):
    try:
        decoded = jwt.decode(
            token,
            client_secret,
            algorithms=['HS256'],
            audience=client_id,
            issuer='launchmystore'
        )
        return {
            'valid': True,
            'shop_id': decoded['sub'],
            'shop_domain': decoded['dest']
        }
    except jwt.InvalidTokenError as e:
        return {
            'valid': False,
            'error': str(e)
        }

def require_session_token(f):
    @wraps(f)
    def decorated(*args, **kwargs):
        auth_header = request.headers.get('Authorization')
        
        if not auth_header or not auth_header.startswith('Bearer '):
            return jsonify({'error': 'Missing authorization'}), 401
        
        token = auth_header[7:]
        result = verify_session_token(
            token,
            os.environ['APP_CLIENT_SECRET'],
            os.environ['APP_CLIENT_ID']
        )
        
        if not result['valid']:
            return jsonify({'error': 'Invalid token'}), 401
        
        request.shop_id = result['shop_id']
        return f(*args, **kwargs)
    
    return decorated

@app.route('/api/data')
@require_session_token
def get_data():
    shop_id = request.shop_id
    # Process request

PHP Verification

<?php
use Firebase\JWT\JWT;
use Firebase\JWT\Key;

function verifySessionToken($token, $clientSecret, $clientId) {
    try {
        $decoded = JWT::decode($token, new Key($clientSecret, 'HS256'));
        
        // Verify audience
        if ($decoded->aud !== $clientId) {
            throw new Exception('Invalid audience');
        }
        
        // Verify issuer
        if ($decoded->iss !== 'launchmystore') {
            throw new Exception('Invalid issuer');
        }
        
        return [
            'valid' => true,
            'shopId' => $decoded->sub,
            'shopDomain' => $decoded->dest
        ];
    } catch (Exception $e) {
        return [
            'valid' => false,
            'error' => $e->getMessage()
        ];
    }
}

// Middleware
function requireSessionToken() {
    $headers = getallheaders();
    $authHeader = $headers['Authorization'] ?? '';
    
    if (!str_starts_with($authHeader, 'Bearer ')) {
        http_response_code(401);
        echo json_encode(['error' => 'Missing authorization']);
        exit;
    }
    
    $token = substr($authHeader, 7);
    $result = verifySessionToken(
        $token,
        getenv('APP_CLIENT_SECRET'),
        getenv('APP_CLIENT_ID')
    );
    
    if (!$result['valid']) {
        http_response_code(401);
        echo json_encode(['error' => 'Invalid token']);
        exit;
    }
    
    return $result;
}

Common Patterns

Fetcher with Auto-Token

Create a fetch wrapper that automatically includes the token:
import { createApp } from '@launchmystore/app-bridge';

const app = createApp({
  apiKey: 'your-client-id',
  host: new URLSearchParams(location.search).get('host')
});

async function authenticatedFetch(url, options = {}) {
  const token = await app.getSessionToken();
  
  return fetch(url, {
    ...options,
    headers: {
      ...options.headers,
      'Authorization': `Bearer ${token}`,
      'Content-Type': 'application/json'
    }
  });
}

// Usage
const response = await authenticatedFetch('/api/products');
const data = await response.json();

React Query Integration

import { useQuery, useMutation } from '@tanstack/react-query';
import { useSessionToken } from '@launchmystore/app-bridge-react';

function useAuthenticatedQuery(key, fetchFn) {
  const { token } = useSessionToken();
  
  return useQuery({
    queryKey: [key, token],
    queryFn: () => fetchFn(token),
    enabled: !!token
  });
}

function ProductList() {
  const { data, isLoading, error } = useAuthenticatedQuery(
    'products',
    async (token) => {
      const response = await fetch('/api/products', {
        headers: { 'Authorization': `Bearer ${token}` }
      });
      return response.json();
    }
  );
  
  if (isLoading) return <Loading />;
  if (error) return <Error />;
  
  return <ProductGrid products={data} />;
}

Axios Interceptor

import axios from 'axios';
import { createApp } from '@launchmystore/app-bridge';

const app = createApp({
  apiKey: 'your-client-id',
  host: new URLSearchParams(location.search).get('host')
});

const api = axios.create({
  baseURL: '/api'
});

api.interceptors.request.use(async (config) => {
  const token = await app.getSessionToken();
  config.headers.Authorization = `Bearer ${token}`;
  return config;
});

// Usage
const { data } = await api.get('/products');

Security Best Practices

Never trust client-side token validation alone. Always verify tokens on your backend.
Session tokens contain sensitive data. Never log them or expose them in error messages.
Always transmit tokens over HTTPS to prevent interception.
Use the sub claim (shop ID) to scope all data access. Never allow cross-shop access.
If a token is expired, request a new one rather than failing the entire operation.
Never expose your client secret in frontend code. Only use it on your backend.

Error Handling

Token Request Errors

try {
  const token = await app.getSessionToken();
} catch (error) {
  switch (error.code) {
    case 'NOT_EMBEDDED':
      // App is not running in an iframe
      redirectToOAuth();
      break;
    case 'HOST_INVALID':
      // Invalid host parameter
      showError('Invalid session. Please reload.');
      break;
    case 'TIMEOUT':
      // Token request timed out
      showError('Connection timeout. Please try again.');
      break;
    default:
      showError('Authentication error. Please reload.');
  }
}

Backend Verification Errors

ErrorCauseSolution
TokenExpiredErrorToken has expiredClient should request new token
JsonWebTokenErrorInvalid signatureCheck client secret matches
NotBeforeErrorToken not yet validCheck server clock sync
// Express error handling
app.use((err, req, res, next) => {
  if (err.name === 'UnauthorizedError') {
    return res.status(401).json({
      error: 'invalid_token',
      message: 'Token is invalid or expired'
    });
  }
  next(err);
});

Debugging

Decode Token (Development Only)

// WARNING: Only for debugging, don't verify this way in production
function decodeToken(token) {
  const [, payload] = token.split('.');
  return JSON.parse(atob(payload));
}

const token = await app.getSessionToken();
console.log(decodeToken(token));
// { iss: 'launchmystore', sub: 'shop_123', ... }

Check Token Status

function isTokenExpired(token) {
  try {
    const [, payload] = token.split('.');
    const { exp } = JSON.parse(atob(payload));
    return Date.now() >= exp * 1000;
  } catch {
    return true;
  }
}

function getTokenTTL(token) {
  try {
    const [, payload] = token.split('.');
    const { exp } = JSON.parse(atob(payload));
    return Math.max(0, exp * 1000 - Date.now());
  } catch {
    return 0;
  }
}

const token = await app.getSessionToken();
console.log('Expires in:', getTokenTTL(token) / 1000, 'seconds');

See Also