Error Handling

Guide to handling errors in the AuthOS SDK with examples for common error scenarios

Updated Dec 16, 2025
Edit on GitHub

Error Handling

The AuthOS SDK provides structured error handling through the SsoApiError class. All API errors include an error code, status code, message, and timestamp for consistent error handling across your application.

The SsoApiError Class

All API errors thrown by the SDK are instances of SsoApiError, which extends the standard JavaScript Error class with additional properties:

class SsoApiError extends Error {
  statusCode: number;      // HTTP status code (e.g., 401, 403, 404)
  errorCode: string;       // Specific error code (e.g., "TOKEN_EXPIRED")
  timestamp: string;       // ISO 8601 timestamp when error occurred
}

Basic Error Handling

import { SsoClient, SsoApiError } from '@drmhse/sso-sdk';

const sso = new SsoClient({ baseURL: 'https://sso.example.com' });

try {
  const profile = await sso.user.getProfile();
  console.log(profile);
} catch (error) {
  if (error instanceof SsoApiError) {
    console.error(`Error: ${error.message}`);
    console.error(`Status: ${error.statusCode}`);
    console.error(`Code: ${error.errorCode}`);
    console.error(`Timestamp: ${error.timestamp}`);
  } else {
    // Network error or other non-API error
    console.error('Unexpected error:', error);
  }
}

Common Error Codes

The following error codes are commonly returned by the API:

Authentication Errors

Error Code Status Description Resolution
TOKEN_EXPIRED 401 JWT access token has expired Refresh the token using sso.auth.refreshToken()
UNAUTHORIZED 401 Invalid or missing authentication credentials Re-authenticate the user
JWT_ERROR 401 Malformed or invalid JWT token Clear stored token and re-authenticate

Authorization Errors

Error Code Status Description Resolution
FORBIDDEN 403 User lacks required permissions Check user role and permissions
ORGANIZATION_NOT_ACTIVE 403 Organization is pending approval or suspended Contact platform administrator

Resource Errors

Error Code Status Description Resolution
NOT_FOUND 404 Requested resource does not exist Verify resource identifiers

Device Flow Errors

Error Code Status Description Resolution
DEVICE_CODE_PENDING 400 User has not authorized the device yet Continue polling
DEVICE_CODE_EXPIRED 400 Device code has expired Request a new device code

Validation Errors

Error Code Status Description Resolution
BAD_REQUEST 400 Invalid request parameters Check request payload
SERVICE_LIMIT_EXCEEDED 400 Service creation limit reached for organization tier Upgrade organization tier
TEAM_LIMIT_EXCEEDED 400 Team member limit reached for organization tier Upgrade organization tier
INVITATION_EXPIRED 400 Invitation link has expired Request a new invitation

Server Errors

Error Code Status Description Resolution
INTERNAL_SERVER_ERROR 500 Unexpected server error Retry request or contact support
DATABASE_ERROR 500 Database operation failed Retry request or contact support
OAUTH_ERROR 500 OAuth provider communication failed Retry authentication flow
STRIPE_ERROR 500 Billing system error Contact support

Error Handling Patterns

Token Expiration Handling

Automatically refresh expired tokens before retrying the request:

async function makeAuthenticatedRequest<T>(
  requestFn: () => Promise<T>
): Promise<T> {
  try {
    return await requestFn();
  } catch (error) {
    if (error instanceof SsoApiError && error.errorCode === 'TOKEN_EXPIRED') {
      // Token expired - refresh it
      const refreshToken = localStorage.getItem('sso_refresh_token');
      if (refreshToken) {
        const tokens = await sso.auth.refreshToken(refreshToken);
        sso.setAuthToken(tokens.access_token);
        localStorage.setItem('sso_access_token', tokens.access_token);
        localStorage.setItem('sso_refresh_token', tokens.refresh_token);

        // Retry the original request
        return await requestFn();
      }
      // No refresh token - redirect to login
      window.location.href = '/login';
      throw error;
    }
    throw error;
  }
}

// Usage
const profile = await makeAuthenticatedRequest(() => sso.user.getProfile());

Device Flow Error Handling

Handle device flow polling with proper error handling:

const pollForToken = async (deviceCode: string, clientId: string) => {
  const maxAttempts = 120; // 10 minutes with 5-second intervals
  let attempts = 0;

  const poll = async (): Promise<TokenResponse> => {
    try {
      const tokens = await sso.auth.deviceCode.exchangeToken({
        grant_type: 'urn:ietf:params:oauth:grant-type:device_code',
        device_code: deviceCode,
        client_id: clientId
      });
      return tokens;
    } catch (error) {
      if (error instanceof SsoApiError) {
        if (error.errorCode === 'DEVICE_CODE_PENDING') {
          // User hasn't authorized yet - continue polling
          attempts++;
          if (attempts >= maxAttempts) {
            throw new Error('Device authorization timeout - please try again');
          }
          await new Promise(resolve => setTimeout(resolve, 5000));
          return poll();
        } else if (error.errorCode === 'DEVICE_CODE_EXPIRED') {
          throw new Error('Device code expired - please restart the login process');
        }
      }
      throw error;
    }
  };

  return poll();
};

Permission Error Handling

Display user-friendly messages for permission errors:

async function performAdminAction() {
  try {
    await sso.organizations.delete('acme-corp');
  } catch (error) {
    if (error instanceof SsoApiError) {
      if (error.errorCode === 'FORBIDDEN') {
        alert('You do not have permission to delete this organization. Contact your organization admin.');
        return;
      } else if (error.errorCode === 'ORGANIZATION_NOT_ACTIVE') {
        alert('This organization is not active. Please contact platform support.');
        return;
      }
    }
    // Generic error handling
    alert('An error occurred. Please try again later.');
  }
}

Resource Not Found Handling

Gracefully handle missing resources:

async function loadOrganization(slug: string) {
  try {
    const org = await sso.organizations.get(slug);
    return org;
  } catch (error) {
    if (error instanceof SsoApiError && error.errorCode === 'NOT_FOUND') {
      // Organization doesn't exist - redirect to organization list
      console.warn(`Organization '${slug}' not found`);
      window.location.href = '/organizations';
      return null;
    }
    throw error;
  }
}

Validation Error Handling

Handle validation errors with specific feedback:

async function createService(orgSlug: string, serviceData: any) {
  try {
    const service = await sso.services.create(orgSlug, serviceData);
    return service;
  } catch (error) {
    if (error instanceof SsoApiError) {
      if (error.errorCode === 'SERVICE_LIMIT_EXCEEDED') {
        alert('Service limit reached for your organization tier. Please upgrade to create more services.');
        return null;
      } else if (error.errorCode === 'BAD_REQUEST') {
        alert(`Invalid service data: ${error.message}`);
        return null;
      }
    }
    throw error;
  }
}

Helper Methods

The SsoApiError class provides convenience methods for common checks:

try {
  await sso.user.getProfile();
} catch (error) {
  if (error instanceof SsoApiError) {
    if (error.isAuthError()) {
      // Handle authentication errors (401, TOKEN_EXPIRED, UNAUTHORIZED)
      redirectToLogin();
    } else if (error.isForbidden()) {
      // Handle permission errors (403, FORBIDDEN)
      showPermissionDeniedMessage();
    } else if (error.isNotFound()) {
      // Handle not found errors (404, NOT_FOUND)
      showResourceNotFoundMessage();
    }
  }
}

Available Helper Methods

  • error.is(errorCode: string): Check for specific error code
  • error.isAuthError(): Check if error is authentication-related
  • error.isForbidden(): Check if error is permission-related
  • error.isNotFound(): Check if error is resource not found

Global Error Handler

Implement a global error handler for consistent error handling across your application:

import { SsoApiError } from '@drmhse/sso-sdk';

class ErrorHandler {
  static handle(error: unknown): void {
    if (error instanceof SsoApiError) {
      // Log to monitoring service
      console.error('AuthOS API Error:', {
        code: error.errorCode,
        status: error.statusCode,
        message: error.message,
        timestamp: error.timestamp
      });

      // Handle specific error types
      if (error.isAuthError()) {
        this.handleAuthError(error);
      } else if (error.isForbidden()) {
        this.handlePermissionError(error);
      } else {
        this.handleGenericError(error);
      }
    } else {
      // Handle non-API errors
      console.error('Unexpected error:', error);
      this.showGenericErrorMessage();
    }
  }

  private static handleAuthError(error: SsoApiError): void {
    // Clear tokens and redirect to login
    localStorage.removeItem('sso_access_token');
    localStorage.removeItem('sso_refresh_token');
    window.location.href = '/login';
  }

  private static handlePermissionError(error: SsoApiError): void {
    alert('You do not have permission to perform this action.');
  }

  private static handleGenericError(error: SsoApiError): void {
    alert(`An error occurred: ${error.message}`);
  }

  private static showGenericErrorMessage(): void {
    alert('An unexpected error occurred. Please try again later.');
  }
}

// Usage
try {
  await sso.user.getProfile();
} catch (error) {
  ErrorHandler.handle(error);
}

Best Practices

  1. Always check error types: Use instanceof SsoApiError to distinguish API errors from network or other errors.

  2. Use error codes, not status codes: Error codes are more specific than HTTP status codes. Use error.errorCode instead of error.statusCode for conditional logic.

  3. Handle token expiration gracefully: Implement automatic token refresh before redirecting users to login.

  4. Provide user-friendly messages: Don’t expose raw error messages to end users. Map error codes to user-friendly messages.

  5. Log errors for debugging: Always log errors with full context (code, status, message, timestamp) for debugging.

  6. Implement retry logic: For transient errors (network issues, server errors), implement exponential backoff retry logic.

  7. Use helper methods: Leverage the built-in helper methods (isAuthError(), isForbidden(), etc.) for cleaner code.