Sessions

Manage user sessions, tokens, and authentication state.

Overview

Sessions track authenticated users across requests. Transactional Auth manages sessions with configurable security policies.

Session Types

TypeStorageUse Case
Token-basedClient-sideSPAs, Mobile apps
Cookie-basedServer-sideServer-rendered apps

Token Structure

Access Token

Short-lived token for API requests:

{
  "iss": "https://auth.usetransactional.com",
  "sub": "user_xxx",
  "aud": "your_client_id",
  "exp": 1704067200,
  "iat": 1704063600,
  "scope": "openid profile email",
  "azp": "your_client_id"
}

Refresh Token

Long-lived token for obtaining new access tokens:

  • Stored securely server-side or in secure storage
  • Rotated on each use (theft detection)
  • Can be revoked to end session

ID Token

Contains user identity claims:

{
  "iss": "https://auth.usetransactional.com",
  "sub": "user_xxx",
  "aud": "your_client_id",
  "exp": 1704067200,
  "iat": 1704063600,
  "nonce": "random-nonce",
  "email": "user@example.com",
  "email_verified": true,
  "name": "John Doe",
  "picture": "https://example.com/avatar.jpg"
}

Managing Sessions

List User Sessions

TypeScript:

const sessions = await client.auth.sessions.list('user_xxx');
 
for (const session of sessions) {
  console.log(`Session: ${session.id}`);
  console.log(`Created: ${session.createdAt}`);
  console.log(`Last Active: ${session.lastActiveAt}`);
  console.log(`IP: ${session.ipAddress}`);
  console.log(`User Agent: ${session.userAgent}`);
}

Python:

sessions = client.auth.sessions.list("user_xxx")
 
for session in sessions:
    print(f"Session: {session.id}")
    print(f"IP: {session.ip_address}")

Get Current Session

const session = await client.auth.sessions.getCurrent(accessToken);
console.log('Current session:', session.id);

Revoke a Session

// Revoke specific session
await client.auth.sessions.revoke('session_xxx');

Revoke All Sessions

// Log out user from all devices
await client.auth.sessions.revokeAll('user_xxx');

Revoke by Refresh Token

await client.auth.tokens.revoke(refreshToken);

Token Refresh

Refresh access tokens before they expire:

async function refreshTokens(refreshToken: string) {
  const response = await fetch('https://auth.usetransactional.com/token', {
    method: 'POST',
    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
    body: new URLSearchParams({
      grant_type: 'refresh_token',
      client_id: 'your_client_id',
      refresh_token: refreshToken,
    }),
  });
 
  const tokens = await response.json();
 
  // IMPORTANT: Use the new refresh token
  return {
    accessToken: tokens.access_token,
    refreshToken: tokens.refresh_token, // New token!
  };
}

Automatic Token Refresh

class TokenManager {
  private accessToken: string;
  private refreshToken: string;
  private expiresAt: number;
 
  async getAccessToken(): Promise<string> {
    // Refresh if expiring within 5 minutes
    if (Date.now() > this.expiresAt - 300000) {
      await this.refresh();
    }
    return this.accessToken;
  }
 
  private async refresh() {
    const tokens = await refreshTokens(this.refreshToken);
    this.accessToken = tokens.accessToken;
    this.refreshToken = tokens.refreshToken;
    this.expiresAt = Date.now() + 3600000; // 1 hour
  }
}

Session Policies

Configure session behavior per organization.

Concurrent Sessions

Limit active sessions per user:

await client.auth.security.update({
  sessions: {
    maxConcurrent: 3, // Max 3 simultaneous sessions
  },
});

When limit is reached:

  • New login succeeds
  • Oldest session is automatically revoked

Idle Timeout

Revoke sessions after inactivity:

await client.auth.security.update({
  sessions: {
    idleTimeout: 60, // Minutes
  },
});

Absolute Timeout

Maximum session duration regardless of activity:

await client.auth.security.update({
  sessions: {
    absoluteTimeout: 480, // 8 hours max
  },
});

Token Validation

Validate Access Token

async function validateToken(token: string) {
  try {
    const response = await fetch('https://auth.usetransactional.com/token/introspection', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/x-www-form-urlencoded',
        'Authorization': `Basic ${btoa(`${clientId}:${clientSecret}`)}`,
      },
      body: new URLSearchParams({ token }),
    });
 
    const result = await response.json();
    return result.active;
  } catch {
    return false;
  }
}

Validate Locally (JWT)

For better performance, validate JWTs locally:

import jwt from 'jsonwebtoken';
 
const jwks = await fetchJWKS('https://auth.usetransactional.com/.well-known/jwks.json');
 
function validateLocally(token: string) {
  try {
    const decoded = jwt.verify(token, getPublicKey(jwks), {
      algorithms: ['RS256'],
      audience: 'your_client_id',
      issuer: 'https://auth.usetransactional.com',
    });
    return { valid: true, claims: decoded };
  } catch (error) {
    return { valid: false, error: error.message };
  }
}

Session Information

Track session metadata:

FieldDescription
ipAddressIP address at login
userAgentBrowser/client info
locationGeo-location (if available)
deviceTypeDesktop, mobile, tablet
createdAtSession start time
lastActiveAtLast activity time
expiresAtSession expiration
const session = await client.auth.sessions.get('session_xxx');
 
console.log('Device:', session.deviceType);
console.log('Location:', session.location?.city);
console.log('Created:', session.createdAt);

Logout Implementation

Single Device Logout

app.post('/logout', async (req, res) => {
  // Revoke current refresh token
  await client.auth.tokens.revoke(req.session.refreshToken);
 
  // Clear server-side session
  req.session.destroy();
 
  // Redirect to home
  res.redirect('/');
});

All Devices Logout

app.post('/logout-all', async (req, res) => {
  const userId = req.user.sub;
 
  // Revoke all sessions
  await client.auth.sessions.revokeAll(userId);
 
  // Clear current session
  req.session.destroy();
 
  res.redirect('/');
});

Federated Logout (SSO)

app.get('/logout', async (req, res) => {
  // Revoke local session
  await client.auth.tokens.revoke(req.session.refreshToken);
  req.session.destroy();
 
  // Redirect to IdP logout
  const logoutUrl = `https://auth.usetransactional.com/logout?` + new URLSearchParams({
    client_id: 'your_client_id',
    post_logout_redirect_uri: 'https://yourapp.com',
  });
 
  res.redirect(logoutUrl);
});

Session Security

Refresh Token Rotation

Tokens are rotated on each refresh. If a stolen token is used:

  1. Valid token is used first → New tokens issued
  2. Stolen token is used → Detected as replay
  3. All tokens for that session are revoked

Token Theft Detection

// When refresh fails due to rotation
if (error.code === 'TOKEN_REUSE_DETECTED') {
  // Possible token theft - force re-authentication
  await client.auth.sessions.revokeAll(userId);
  redirectToLogin();
}

Next Steps

  • MFA - Add multi-factor authentication
  • Security - Configure security policies
  • Webhooks - Track session events