Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/scalekit-inc/developer-docs/llms.txt

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

Proper token management is critical for application security. This guide covers best practices for handling access tokens, refresh tokens, and ID tokens.

Token types

Access tokens

  • Lifetime: Short-lived (5-60 minutes)
  • Purpose: Authorize API requests
  • Storage: HttpOnly cookies or Authorization headers
  • Validation: Required on every request

Refresh tokens

  • Lifetime: Long-lived (days to months)
  • Purpose: Obtain new access tokens
  • Storage: Secure HttpOnly cookies
  • Rotation: Single-use with automatic rotation

ID tokens

  • Lifetime: Varies (usually matches access token)
  • Purpose: User identity information
  • Storage: Cookies or secure storage
  • Usage: Logout flows and user profile

Secure storage

Web applications

Use HttpOnly cookies with security attributes:
res.cookie('accessToken', token, {
  httpOnly: true,   // Prevent XSS
  secure: true,     // HTTPS only
  sameSite: 'strict', // CSRF protection
  maxAge: 300000    // 5 minutes
});

Single-page applications

  • Store access tokens in memory
  • Use Authorization headers for API calls
  • Never use localStorage or sessionStorage
  • Refresh tokens in HttpOnly cookies

Mobile applications

  • iOS: Use Keychain Services
  • Android: Use KeyStore system
  • Implement certificate pinning
  • Clear tokens on logout

Token encryption

Encrypt tokens before storage:
const crypto = require('crypto');

function encrypt(token) {
  const algorithm = 'aes-256-gcm';
  const key = Buffer.from(process.env.ENCRYPTION_KEY, 'hex');
  const iv = crypto.randomBytes(16);
  
  const cipher = crypto.createCipheriv(algorithm, key, iv);
  let encrypted = cipher.update(token, 'utf8', 'hex');
  encrypted += cipher.final('hex');
  
  const authTag = cipher.getAuthTag();
  return `${iv.toString('hex')}:${encrypted}:${authTag.toString('hex')}`;
}

function decrypt(encryptedToken) {
  const [ivHex, encrypted, authTagHex] = encryptedToken.split(':');
  const algorithm = 'aes-256-gcm';
  const key = Buffer.from(process.env.ENCRYPTION_KEY, 'hex');
  const iv = Buffer.from(ivHex, 'hex');
  const authTag = Buffer.from(authTagHex, 'hex');
  
  const decipher = crypto.createDecipheriv(algorithm, key, iv);
  decipher.setAuthTag(authTag);
  
  let decrypted = decipher.update(encrypted, 'hex', 'utf8');
  decrypted += decipher.final('utf8');
  
  return decrypted;
}

Token validation

Validate tokens on every protected request:
async function validateRequest(req, res, next) {
  const accessToken = req.cookies.accessToken;
  
  if (!accessToken) {
    return res.status(401).json({ error: 'No token provided' });
  }
  
  try {
    const decrypted = decrypt(accessToken);
    const isValid = await scalekit.validateAccessToken(decrypted);
    
    if (!isValid) {
      // Attempt token refresh
      return await refreshToken(req, res, next);
    }
    
    next();
  } catch (error) {
    return res.status(401).json({ error: 'Invalid token' });
  }
}

Token refresh

Refresh tokens transparently in middleware:
async function refreshToken(req, res, next) {
  const refreshToken = req.cookies.refreshToken;
  
  if (!refreshToken) {
    return res.status(401).json({ error: 'Session expired' });
  }
  
  try {
    const decrypted = decrypt(refreshToken);
    const authResult = await scalekit.refreshAccessToken(decrypted);
    
    // Update cookies with new tokens
    res.cookie('accessToken', encrypt(authResult.accessToken), {
      httpOnly: true,
      secure: true,
      sameSite: 'strict',
      maxAge: (authResult.expiresIn - 60) * 1000
    });
    
    res.cookie('refreshToken', encrypt(authResult.refreshToken), {
      httpOnly: true,
      secure: true,
      sameSite: 'strict'
    });
    
    next();
  } catch (error) {
    return res.status(401).json({ error: 'Refresh failed' });
  }
}

Token rotation

Implement refresh token rotation to detect theft:
  • Issue new refresh token on each refresh
  • Invalidate old refresh token immediately
  • Track token families to detect concurrent use
  • Revoke all tokens if theft detected

Security best practices

Never log tokens

// BAD - Tokens in logs
console.log('Access token:', accessToken);

// GOOD - Redacted logging
console.log('Access token:', accessToken.substring(0, 10) + '...');

Validate token claims

const claims = jwt.decode(accessToken);

if (claims.exp < Date.now() / 1000) {
  throw new Error('Token expired');
}

if (claims.iss !== expectedIssuer) {
  throw new Error('Invalid issuer');
}

if (!claims.aud.includes(clientId)) {
  throw new Error('Invalid audience');
}

Handle clock skew

const CLOCK_SKEW_SECONDS = 60;

function isTokenExpired(token) {
  const claims = jwt.decode(token);
  const now = Math.floor(Date.now() / 1000);
  return claims.exp < (now - CLOCK_SKEW_SECONDS);
}

Implement rate limiting

const rateLimit = require('express-rate-limit');

const refreshLimiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 5, // 5 refresh attempts
  message: 'Too many refresh attempts'
});

app.post('/auth/refresh', refreshLimiter, refreshHandler);

Token revocation

Revoke tokens when needed:
// Revoke specific session
await scalekit.session.revokeSession(sessionId);

// Revoke all user sessions
await scalekit.session.revokeAllUserSessions(userId);

// Clear client-side tokens
res.clearCookie('accessToken');
res.clearCookie('refreshToken');

Next steps

Session policies

Configure session timeouts

Best practices

Authentication security guide

Session management

Session concepts