Session Token Flow
Session Token Flow
This page documents the complete lifecycle of session tokens for embedded apps, from the moment a merchant navigates to your app page through ongoing token refresh.
Overview
Session tokens are short-lived JWTs that authenticate embedded app iframes to the SeloraX platform API. They are:
- Signed with HMAC-SHA256 using your app's
session_signing_key - Valid for 10 minutes
- Delivered via the App Bridge postMessage protocol
- Verified server-side through the platform's session verification endpoint
Complete Flow
Step 1: Merchant Navigates to App
The merchant opens your app's page within the SeloraX dashboard (e.g., /22/settings/apps/messaging).
Step 2: Dashboard Requests Embed Parameters
The dashboard frontend calls the platform API to get the iframe URL and initial session token:
GET /api/apps/session/embed-params?app_id=X&store_id=Y
Step 3: Backend Generates Embed Data
The platform generates two things:
-
HMAC-signed iframe URL — Your app's
app_urlwith signed query parameters:https://app.example.com?store_id=22&host=<base64>×tamp=<unix>&hmac=<sha256> -
JWT session token — Signed with your app's
session_signing_keyusing HS256, with a 10-minute TTL.
Step 4: Dashboard Loads the Iframe
The dashboard sets the iframe src to the HMAC-signed URL. Your app begins loading.
Step 5: App Verifies HMAC Parameters
When your app loads, it should verify the HMAC signature on the URL parameters to confirm the request originated from SeloraX. This prevents unauthorized embedding.
Step 6: App Sends Ready Signal
Once loaded and verified, your app sends the handshake message:
window.parent.postMessage({ type: 'app-bridge:ready' }, '*');Step 7: Dashboard Delivers Session Token
The dashboard responds with the session token:
// Dashboard sends:
iframe.contentWindow.postMessage({
type: 'selorax:session-token',
token: '<jwt>'
}, appOrigin);Step 8: App Uses Token for API Calls
Your app includes the token in API requests:
Authorization: Bearer <session_token>
Flow Diagram
Merchant Dashboard Platform API Your App
| | | |
|-- opens app page ---->| | |
| |-- GET embed-params ---->| |
| | |-- generates HMAC URL |
| | |-- generates JWT |
| |<-- { iframe_url, token }| |
| | | |
| |-- sets iframe src ---------------------------------->|
| | | |
| | | verifies HMAC params
| | | |
| |<------------- app-bridge:ready ----------------|
| | | |
| |------------- selorax:session-token ----------->|
| | | |
| | |<-- API call + Bearer |
| | |-- response --------->|
Token Refresh Cycle
Session tokens expire after 10 minutes. When your app receives a 401 Unauthorized from the API:
- App sends
selorax:request-session-tokenvia postMessage - Dashboard calls
POST /api/apps/session/session-tokento generate a fresh JWT - Dashboard delivers the new token via
selorax:session-token - App retries the failed request with the new token
Your App Dashboard Platform API
| | |
|-- API call + Bearer ----|------------------------>|
| | |
|<-- 401 Unauthorized ----|-------------------------|
| | |
|-- request-session-token | |
| |-- POST session-token -->|
| |<-- new JWT -------------|
| | |
|<-- selorax:session-token| |
| | |
|-- retry API call -------|------------------------>|
| | |
|<-- 200 OK --------------|-------------------------|
HMAC Verification (App Side)
When your app loads inside the iframe, verify the URL parameters to ensure the request came from SeloraX.
const crypto = require('crypto');
function verifyHmacParams(query, signingKey) {
const { hmac, ...params } = query;
// Sort parameters alphabetically and build the message string
const message = Object.keys(params)
.sort()
.map(key => `${key}=${params[key]}`)
.join('&');
// Compute expected HMAC
const expected = crypto
.createHmac('sha256', signingKey)
.update(message)
.digest('hex');
// Compare signatures
if (hmac !== expected) return false;
// Check timestamp is within 5-minute window
const timestamp = parseInt(params.timestamp);
const now = Math.floor(Date.now() / 1000);
if (Math.abs(now - timestamp) > 300) return false;
return true;
}Parameters included in the HMAC:
| Parameter | Description |
|---|---|
store_id | The merchant's store ID |
host | Base64-encoded dashboard host |
timestamp | Unix timestamp (seconds) when the URL was generated |
hmac | SHA-256 HMAC of the sorted parameter string (excluded from signing input) |
Session Token JWT Claims
The session token is a standard JWT (HS256) with these claims:
{
"iss": "https://admin.selorax.io",
"dest": "https://app.example.com",
"aud": "sx_app_1b16e193a28d2640d2d9734dbf4907e8",
"sub": "22",
"sid": "2",
"app_id": 2,
"jti": "550e8400-e29b-41d4-a716-446655440000",
"iat": 1708000000,
"exp": 1708000600
}| Claim | Description |
|---|---|
iss | Issuer — the dashboard origin (from DASHBOARD_URL env var) |
dest | Destination — your app's app_url |
aud | Audience — your app's client_id |
sub | Subject — the merchant's store_id |
sid | Session ID — the installation_id for this app+store |
app_id | Your app's numeric ID |
jti | Unique token identifier (UUID) |
iat | Issued at timestamp |
exp | Expiration timestamp (10 minutes from iat) |
The token is signed with your app's session_signing_key using HMAC-SHA256 (HS256 algorithm).
Server-Side Token Verification
Your app's backend can verify session tokens by calling the platform's verification endpoint.
POST /api/apps/session/verify
Content-Type: application/json
{
"session_token": "<jwt>",
"client_id": "sx_app_...",
"client_secret": "sx_secret_..."
}
Response (200 OK):
{
"message": "Session token verified.",
"data": {
"store_id": 22,
"installation_id": 2,
"app_id": 2
},
"status": 200
}This endpoint is rate limited to 300 requests per minute. Cache the verification result for the token's remaining lifetime to avoid hitting this limit.
Best Practices
- Verify HMAC on every iframe load. Do not skip this step, even in development.
- Cache verified token claims. After verifying a session token once, cache the result keyed by the token's
jtior hash until its expiry. - Request new tokens proactively. If you know a token is close to expiry (e.g., 9 minutes old), request a fresh one before making API calls.
- Handle the 401-refresh cycle gracefully. Queue pending requests while waiting for a new token, then replay them. Avoid requesting multiple tokens simultaneously.
- Validate origins. Always check
event.originin your postMessage handler in production environments.