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 returned by the API and are available in the AuthErrorCodes enum:
Authentication & Authorization Errors
| Error Code | Status | Description |
|---|---|---|
TOKEN_EXPIRED |
401 | JWT access token has expired |
UNAUTHORIZED |
401 | Invalid or missing authentication credentials |
JWT_ERROR |
401 | Malformed or invalid JWT token |
FORBIDDEN |
403 | User lacks required permissions |
ORGANIZATION_NOT_ACTIVE |
403 | Organization is pending approval or suspended |
FEATURE_NOT_AVAILABLE_IN_TIER |
403 | Feature not available in organization’s current tier |
Request & Validation Errors
| Error Code | Status | Description |
|---|---|---|
BAD_REQUEST |
400 | Invalid request parameters or malformed JSON |
DUPLICATE_CONSTRAINT |
400 | A resource with this information already exists |
NOT_FOUND |
404 | Requested resource does not exist |
INVITATION_EXPIRED |
400 | Invitation link has expired |
TOO_MANY_REQUESTS |
429 | Rate limit exceeded |
Device Flow Errors
| Error Code | Status | Description |
|---|---|---|
DEVICE_CODE_PENDING |
400 | Authorization is still pending (keep polling) |
DEVICE_CODE_EXPIRED |
400 | Device code has expired |
Resource Limit Errors
| Error Code | Status | Description |
|---|---|---|
SERVICE_LIMIT_EXCEEDED |
400 | Service creation limit reached for organization tier |
TEAM_LIMIT_EXCEEDED |
400 | Team member limit reached for organization tier |
Server Errors
| Error Code | Status | Description |
|---|---|---|
INTERNAL_SERVER_ERROR |
500 | Unexpected server error |
DATABASE_ERROR |
500 | General database operation failed |
OAUTH_ERROR |
500 | OAuth provider communication failed |
STRIPE_ERROR |
500 | Billing system error |
GENERIC_ERROR |
500 | General system error |
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) {
// sso.auth.refreshToken() automatically updates the session
await sso.auth.refreshToken(refreshToken);
// 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 codeerror.isAuthError(): Check if error is authentication-relatederror.isForbidden(): Check if error is permission-relatederror.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
-
Always check error types: Use
instanceof SsoApiErrorto distinguish API errors from network or other errors. -
Use error codes, not status codes: Error codes are more specific than HTTP status codes. Use
error.errorCodeinstead oferror.statusCodefor conditional logic. -
Handle token expiration gracefully: Implement automatic token refresh before redirecting users to login.
-
Provide user-friendly messages: Don’t expose raw error messages to end users. Map error codes to user-friendly messages.
-
Log errors for debugging: Always log errors with full context (code, status, message, timestamp) for debugging.
-
Implement retry logic: For transient errors (network issues, server errors), implement exponential backoff retry logic.
-
Use helper methods: Leverage the built-in helper methods (
isAuthError(),isForbidden(), etc.) for cleaner code.