RegisterLogin
DocsPricing
RegisterLogin
  • Getting Started
  • Introduction
  • Quick Start
  • SDKs
  • React
  • TypeScript
  • Next.js
  • Express
  • NestJS
  • Python
  • API Reference
  • Support and Resources
  • FAQ
  • Contact Support

AuthSafe

Product

HighlightFeatureIntegrationPricingFAQ

Company

AboutBlogContactSitemap

Developer

DashboardDocumentation

Legal

Terms & ConditionsPrivacyComplianceShippingCancellation

© 2026 AuthSafe. All rights reserved.

We value your privacy

This website uses cookies for anonymous analytics to help us improve your experience. No personal information is stored or shared. You can allow or reject analytics tracking at any time. See our Privacy Policy.

We use cookies for anonymous analytics. No personal info is stored. See our Privacy Policy.

JWT Utilities

JWT token verification and utility functions with JWKS caching.

Overview

The JWT utilities provide functions for verifying, decoding, and inspecting JWT tokens from AuthSafe:
  1. verifyToken() - Verify JWT signature with JWKS
  2. decodeToken() - Decode JWT without verification
  3. extractUserId() - Extract user ID from subject
  4. isClientToken() - Check if token is M2M client
  5. isTokenExpired() - Check token expiration
  6. shouldRefreshToken() - Check if refresh needed

Import

import {
  verifyToken,
  decodeToken,
  extractUserId,
  isClientToken,
  isTokenExpired,
  shouldRefreshToken,
} from 'authsafe-express';

verifyToken()

Verify JWT signature using JWKS (JSON Web Key Set) and validate claims.
async function verifyToken(
  token: string,
  domain: string,
  clientId: string,
  organizationId: string,
): Promise<AuthSafeJWTPayload>;

Parameters

ParameterTypeDescription
tokenstringJWT token to verify
domainstringAuthSafe domain (issuer)
clientIdstringExpected audience claim
organizationIdstringOrganization ID for JWKS URL

Usage

import { verifyToken, decodeToken } from 'authsafe-express';

app.post('/api/verify', async (req, res) => {
  const { token } = req.body;

  try {
    // First decode to get organization ID
    const decoded = decodeToken(token);
    if (!decoded) {
      return res.status(400).json({ error: 'Invalid token format' });
    }

    // Verify signature and claims
    const payload = await verifyToken(
      token,
      process.env.AUTHSAFE_DOMAIN!,
      process.env.AUTHSAFE_CLIENT_ID!,
      decoded.org_id,
    );

    res.json({ valid: true, payload });
  } catch (error) {
    res.status(401).json({ valid: false, error: 'Invalid token' });
  }
});

JWKS Caching

The SDK automatically caches JWKS keys for optimal performance:
  • Cache Duration: 1 hour
  • Cache Key: {domain}:{organizationId}
  • Automatic Refresh: Cached keys are refreshed when expired
This means subsequent verifications use cached keys for fast validation.

Verification Checks

The function verifies:
  1. Signature - RS256 algorithm with JWKS public key
  2. Issuer - Matches domain
  3. Audience - Matches clientId
  4. Expiration - Token not expired
  5. Not Before - Token is active

decodeToken()

Decode JWT without verifying signature. Useful for extracting organization ID before verification.
function decodeToken(token: string): AuthSafeJWTPayload | null;

Usage

import { decodeToken } from 'authsafe-express';

app.get('/api/token-info', (req, res) => {
  const token = req.headers.authorization?.replace('Bearer ', '');

  if (!token) {
    return res.status(401).json({ error: 'No token provided' });
  }

  const payload = decodeToken(token);

  if (!payload) {
    return res.status(400).json({ error: 'Invalid token format' });
  }

  res.json({
    userId: payload.sub,
    organizationId: payload.org_id,
    expiresAt: payload.exp,
    issuedAt: payload.iat,
  });
});
No Signature Verification
decodeToken() does NOT verify the token signature. Always use verifyToken() for authentication.

Returns

Returns AuthSafeJWTPayload if token is valid Base64url-encoded JWT, otherwise null.

extractUserId()

Extract user ID from JWT subject claim by removing the authsafe| or client| prefix.
function extractUserId(sub: string): string;

Usage

import { extractUserId } from 'authsafe-express';

// Subject: "authsafe|user_123"
const userId = extractUserId('authsafe|user_123');
console.log(userId); // "user_123"

// Subject: "client|app_456"
const clientId = extractUserId('client|app_456');
console.log(clientId); // "app_456"

// No prefix
const rawId = extractUserId('custom_789');
console.log(rawId); // "custom_789"

Example with Decoded Token

import { decodeToken, extractUserId } from 'authsafe-express';

app.get('/api/user-id', (req, res) => {
  const token = req.cookies['authsafe.access_token'];
  const payload = decodeToken(token);

  if (!payload) {
    return res.status(401).json({ error: 'Invalid token' });
  }

  const userId = extractUserId(payload.sub);
  res.json({ userId });
});

isClientToken()

Check if JWT subject represents a machine-to-machine (M2M) client application.
function isClientToken(sub: string): boolean;

Usage

import { isClientToken } from 'authsafe-express';

console.log(isClientToken('authsafe|user_123')); // false
console.log(isClientToken('client|app_456')); // true

Example

import { decodeToken, isClientToken } from 'authsafe-express';

app.post('/api/admin', (req, res) => {
  const token = req.cookies['authsafe.access_token'];
  const payload = decodeToken(token);

  if (!payload) {
    return res.status(401).json({ error: 'Invalid token' });
  }

  // Block M2M clients from accessing user-specific endpoints
  if (isClientToken(payload.sub)) {
    return res.status(403).json({
      error: 'Client credentials not allowed for this endpoint',
    });
  }

  res.json({ message: 'User-only endpoint' });
});

isTokenExpired()

Check if JWT token is expired.
function isTokenExpired(exp: number): boolean;

Parameters

  • exp - Expiration timestamp (Unix seconds from JWT exp claim)

Usage

import { isTokenExpired } from 'authsafe-express';

const expiresAt = 1735689600; // Some future timestamp
console.log(isTokenExpired(expiresAt)); // false

const expiredAt = 1609459200; // Past timestamp
console.log(isTokenExpired(expiredAt)); // true

Example

import { decodeToken, isTokenExpired } from 'authsafe-express';

app.get('/api/check-session', (req, res) => {
  const token = req.cookies['authsafe.access_token'];
  const payload = decodeToken(token);

  if (!payload || isTokenExpired(payload.exp)) {
    return res.status(401).json({
      expired: true,
      message: 'Session expired',
    });
  }

  res.json({
    expired: false,
    expiresAt: payload.exp,
  });
});

shouldRefreshToken()

Check if token should be refreshed (expires in less than 5 minutes).
function shouldRefreshToken(exp: number): boolean;

Parameters

  • exp - Expiration timestamp (Unix seconds from JWT exp claim)

Usage

import { shouldRefreshToken } from 'authsafe-express';

const expiresAt = Math.floor(Date.now() / 1000) + 600; // Expires in 10 minutes
console.log(shouldRefreshToken(expiresAt)); // false

const expiresSoon = Math.floor(Date.now() / 1000) + 200; // Expires in 3.3 minutes
console.log(shouldRefreshToken(expiresSoon)); // true

Example

import {
  getAuthCookies,
  shouldRefreshToken,
  refreshTokens,
  setAuthCookies,
} from 'authsafe-express';

async function autoRefreshMiddleware(req: any, res: any, next: any) {
  const tokens = getAuthCookies(req, authConfig);

  if (!tokens) {
    return next();
  }

  // Refresh if token expires in < 5 minutes
  if (shouldRefreshToken(tokens.expiresAt) && tokens.refreshToken) {
    try {
      const newTokens = await refreshTokens(tokens.refreshToken, {
        clientId: process.env.AUTHSAFE_CLIENT_ID!,
        clientSecret: process.env.AUTHSAFE_CLIENT_SECRET!,
        domain: process.env.AUTHSAFE_DOMAIN!,
      });

      setAuthCookies(res, newTokens, authConfig);
      console.log('Token auto-refreshed');
    } catch (error) {
      console.error('Auto-refresh failed:', error);
    }
  }

  next();
}

AuthSafeJWTPayload Interface

JWT token payload structure:
interface AuthSafeJWTPayload {
  /** Issuer (AuthSafe domain) */
  iss: string;

  /** Subject (user ID with "authsafe|" prefix or "client|" for M2M) */
  sub: string;

  /** Audience (client ID) */
  aud: string;

  /** Issued at (Unix timestamp in seconds) */
  iat: number;

  /** Expiration time (Unix timestamp in seconds) */
  exp: number;

  /** Organization ID */
  org_id: string;

  /** Scopes (access token only) */
  scope?: string[];

  /** Nonce (ID token only) */
  nonce?: string;
}

Complete Examples

Custom Token Verification Middleware

import { verifyToken, decodeToken } from 'authsafe-express';

function verifyBearerToken() {
  return async (req: any, res: any, next: any) => {
    const authHeader = req.headers.authorization;

    if (!authHeader || !authHeader.startsWith('Bearer ')) {
      return res.status(401).json({ error: 'No token provided' });
    }

    const token = authHeader.substring(7);

    try {
      // Decode to get org_id
      const decoded = decodeToken(token);
      if (!decoded) {
        return res.status(401).json({ error: 'Invalid token' });
      }

      // Verify signature
      const payload = await verifyToken(
        token,
        process.env.AUTHSAFE_DOMAIN!,
        process.env.AUTHSAFE_CLIENT_ID!,
        decoded.org_id,
      );

      req.auth = {
        userId: extractUserId(payload.sub),
        organizationId: payload.org_id,
        scopes: payload.scope || [],
        payload,
      };

      next();
    } catch (error) {
      res.status(401).json({ error: 'Invalid or expired token' });
    }
  };
}

// Use in routes
app.get('/api/protected', verifyBearerToken(), (req, res) => {
  res.json({ user: req.auth });
});

Token Inspector

import {
  decodeToken,
  isTokenExpired,
  shouldRefreshToken,
  isClientToken,
  extractUserId,
} from 'authsafe-express';

app.post('/api/inspect-token', (req, res) => {
  const { token } = req.body;
  const payload = decodeToken(token);

  if (!payload) {
    return res.status(400).json({ error: 'Invalid token format' });
  }

  res.json({
    userId: extractUserId(payload.sub),
    organizationId: payload.org_id,
    scopes: payload.scope || [],
    issuedAt: new Date(payload.iat * 1000).toISOString(),
    expiresAt: new Date(payload.exp * 1000).toISOString(),
    isExpired: isTokenExpired(payload.exp),
    shouldRefresh: shouldRefreshToken(payload.exp),
    isClient: isClientToken(payload.sub),
    timeUntilExpiry: payload.exp - Math.floor(Date.now() / 1000),
  });
});

Scope Validation

import { decodeToken } from 'authsafe-express';

function hasScope(requiredScope: string) {
  return (req: any, res: any, next: any) => {
    const token = req.cookies['authsafe.access_token'];
    const payload = decodeToken(token);

    if (!payload || !payload.scope) {
      return res.status(401).json({ error: 'Unauthorized' });
    }

    if (!payload.scope.includes(requiredScope)) {
      return res.status(403).json({
        error: 'Forbidden',
        required: requiredScope,
        available: payload.scope,
      });
    }

    next();
  };
}

app.delete('/api/admin/users/:id', hasScope('admin:delete'), (req, res) => {
  res.json({ message: 'User deleted' });
});

Performance Considerations

JWKS Caching

The SDK caches JWKS keys for 1 hour to minimize network requests:
// First verification: Fetches JWKS from AuthSafe
await verifyToken(token1, domain, clientId, orgId);

// Subsequent verifications: Use cached JWKS (fast)
await verifyToken(token2, domain, clientId, orgId);
Cache key format: {domain}:{organizationId}

Decode Before Verify

Always decode first to extract organization ID:
// ✅ Efficient
const decoded = decodeToken(token);
if (decoded) {
  const verified = await verifyToken(token, domain, clientId, decoded.org_id);
}

// ❌ Won't work - need org_id
await verifyToken(token, domain, clientId, '???');

Security Best Practices

  1. Always verify tokens - Use verifyToken() for authentication
  2. Never trust decoded tokens - Decode is for inspection only
  3. Check expiration - Use isTokenExpired() before trusting
  4. Validate scopes - Check permissions in every request
  5. Use HTTPS - Prevent token interception
  6. Rotate keys regularly - JWKS caching handles this automatically
  7. Log verification failures - Monitor security issues

Troubleshooting

"Invalid token" Error

Ensure token is properly formatted JWT:
const payload = decodeToken(token);
if (!payload) {
  console.error('Token is not valid JWT format');
}

"Signature verification failed"

Check that:
  1. Token was issued by your AuthSafe domain
  2. Organization ID matches
  3. Token hasn't been tampered with

JWKS Fetch Failed

Ensure AuthSafe domain is accessible:
// Verify JWKS URL is reachable
const jwksUrl = `${domain}/auth/.well-known/jwks.json?organization_id=${orgId}`;
console.log('JWKS URL:', jwksUrl);

Related

  • Middleware - Using JWT in route protection
  • Session Management - Token storage
  • Route Handlers - OAuth token exchange
  • Setup - Initial configuration