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:
Caches the current token
Refreshes before expiration
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
Always verify on the backend
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.
Handle expiration gracefully
If a token is expired, request a new one rather than failing the entire operation.
Store client secret securely
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
Error Cause Solution TokenExpiredErrorToken has expired Client should request new token JsonWebTokenErrorInvalid signature Check client secret matches NotBeforeErrorToken not yet valid Check 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