Sigma Auth
OAuth Features

Sigma Auth provides comprehensive error responses following OAuth 2.0 standards with additional context for Bitcoin-specific authentication failures. Proper error handling ensures a smooth user experience and easier debugging.

Error Response Format

All OAuth endpoints return structured error responses:

{
  "error": "invalid_request",
  "error_description": "Missing required parameter: client_id",
  "error_uri": "https://docs.sigmaidentity.com/docs/oauth/error-handling#invalid_request",
  "state": "user_provided_state_value",
  "request_id": "req_abc123xyz789"
}

Response Fields

  • error: OAuth 2.0 standard error code
  • error_description: Human-readable error description
  • error_uri: Link to documentation for this error type
  • state: Echoed back from the original request (CSRF protection)
  • request_id: Unique identifier for tracing and support

Standard OAuth Error Codes

Authorization Endpoint Errors (GET /authorize)

These errors redirect back to your redirect_uri with error parameters:

invalid_request

The request is missing required parameters or is malformed.

{
  "error": "invalid_request", 
  "error_description": "Missing required parameter: client_id"
}

Common causes:

  • Missing client_id, redirect_uri, or response_type
  • Invalid parameter format
  • Malformed request URL
// Handle in your redirect handler
const urlParams = new URLSearchParams(window.location.search);
const error = urlParams.get('error');

if (error === 'invalid_request') {
  const description = urlParams.get('error_description');
  console.error('Authorization request error:', description);
  
  // Show user-friendly message
  showError('There was a problem with the login request. Please try again.');
}

unauthorized_client

The client is not authorized to request an authorization code.

{
  "error": "unauthorized_client",
  "error_description": "Client 'unknown-app' is not registered"
}

Common causes:

  • Client ID not registered with Sigma Auth
  • Client is disabled or suspended
  • Incorrect client ID in request

access_denied

The user or authorization server denied the request.

{
  "error": "access_denied",
  "error_description": "User cancelled authentication"
}

Common causes:

  • User clicked "Cancel" or "Deny"
  • User closed authentication popup
  • Authentication timeout

unsupported_response_type

The authorization server doesn't support the response type.

{
  "error": "unsupported_response_type", 
  "error_description": "Response type 'token' not supported. Use 'code'"
}

invalid_scope

The requested scope is invalid or unknown.

{
  "error": "invalid_scope",
  "error_description": "Unknown scope: 'admin'. Available scopes: openid, profile"
}

server_error

The authorization server encountered an unexpected condition.

{
  "error": "server_error",
  "error_description": "Internal server error occurred",
  "request_id": "req_abc123"
}

Token Endpoint Errors (POST /token)

invalid_request

The request is missing required parameters or is malformed.

{
  "error": "invalid_request",
  "error_description": "Missing required parameter: code"
}

invalid_client

Client authentication failed.

{
  "error": "invalid_client", 
  "error_description": "Client authentication failed"
}

invalid_grant

The provided authorization code is invalid, expired, or revoked.

{
  "error": "invalid_grant",
  "error_description": "Authorization code has expired"
}

Common causes:

  • Code was already used (codes are single-use)
  • Code has expired (10-minute lifetime)
  • Code was issued to a different client
  • PKCE verification failed

unsupported_grant_type

The grant type is not supported.

{
  "error": "unsupported_grant_type",
  "error_description": "Grant type 'password' not supported"
}

Sigma-Specific Error Codes

Bitcoin Authentication Errors

signature_verification_failed

Bitcoin signature verification failed.

{
  "error": "signature_verification_failed",
  "error_description": "Invalid Bitcoin signature for message",
  "details": {
    "pubkey": "02a94e09bcd...",
    "message": "auth_challenge_123",
    "signature": "H1234..."
  }
}

Common causes:

  • Incorrect private key used for signing
  • Message format doesn't match expected format
  • Signature encoding issues
  • Clock skew (message contains timestamp)

key_generation_failed

Bitcoin key generation failed in the browser.

{
  "error": "key_generation_failed",
  "error_description": "Browser crypto API unavailable or blocked"
}

Common causes:

  • Non-HTTPS connection in production
  • Browser doesn't support WebCrypto API
  • Ad blockers interfering with crypto operations
  • Privacy mode restricting crypto access

backup_decryption_failed

Failed to decrypt user's backup file.

{
  "error": "backup_decryption_failed",
  "error_description": "Invalid password or corrupted backup file"
}

invalid_backup_format

Backup file format is not recognized.

{
  "error": "invalid_backup_format",
  "error_description": "Backup file format not supported. Expected WIF, BAP, or encrypted backup"
}

Rate Limiting Errors

rate_limit_exceeded

Rate limit exceeded for the client or IP address.

{
  "error": "rate_limit_exceeded",
  "error_description": "Too many requests. Please try again in 60 seconds",
  "retry_after": 60,
  "limit": 10,
  "window": "1 minute"
}

PKCE Errors

invalid_code_challenge

PKCE code challenge is invalid.

{
  "error": "invalid_request",
  "error_description": "Invalid code_challenge_method. Must be 'S256' or 'plain'"
}

code_verifier_mismatch

PKCE code verifier doesn't match the challenge.

{
  "error": "invalid_grant",
  "error_description": "Code verifier does not match challenge"
}

Error Handling Patterns

React Hook for OAuth Errors

import { useState, useCallback } from 'react';

function useOAuthErrorHandling() {
  const [error, setError] = useState(null);
  const [isRetrying, setIsRetrying] = useState(false);

  const handleOAuthError = useCallback((error, errorDescription, requestId) => {
    const errorInfo = {
      code: error,
      description: errorDescription,
      requestId: requestId,
      timestamp: new Date(),
      userMessage: getUserFriendlyMessage(error, errorDescription)
    };

    setError(errorInfo);
    
    // Log for debugging
    console.error('OAuth Error:', errorInfo);
    
    // Report to monitoring service
    if (window.gtag) {
      window.gtag('event', 'oauth_error', {
        error_code: error,
        error_description: errorDescription,
        request_id: requestId
      });
    }

    return errorInfo;
  }, []);

  const retryAuth = useCallback(async () => {
    if (!error) return;
    
    setIsRetrying(true);
    setError(null);
    
    try {
      // Clear any stored state
      sessionStorage.removeItem('pkce_verifier');
      localStorage.removeItem('auth_state');
      
      // Wait a moment before retrying
      await new Promise(resolve => setTimeout(resolve, 1000));
      
      // Restart auth flow
      window.location.href = createLoginUrl();
    } catch (retryError) {
      console.error('Retry failed:', retryError);
      setError({
        code: 'retry_failed',
        description: 'Failed to restart authentication',
        userMessage: 'Please try again or contact support'
      });
    } finally {
      setIsRetrying(false);
    }
  }, [error]);

  const clearError = useCallback(() => {
    setError(null);
  }, []);

  return { error, isRetrying, handleOAuthError, retryAuth, clearError };
}

function getUserFriendlyMessage(error, description) {
  const messages = {
    'invalid_client': 'App configuration error. Please contact support.',
    'access_denied': 'Sign-in was cancelled. You can try again anytime.',
    'server_error': 'Authentication service temporarily unavailable. Please try again.',
    'rate_limit_exceeded': 'Too many sign-in attempts. Please wait a moment and try again.',
    'signature_verification_failed': 'Bitcoin signature verification failed. Please check your key.',
    'backup_decryption_failed': 'Incorrect password or corrupted backup file.',
    'key_generation_failed': 'Unable to generate Bitcoin key. Please ensure you\'re using HTTPS.',
    'invalid_grant': 'Authentication session expired. Please sign in again.',
    'invalid_request': 'Invalid authentication request. Please try again.'
  };

  return messages[error] || description || 'An authentication error occurred.';
}

Error Display Component

function AuthErrorDisplay({ error, onRetry, onClear }) {
  if (!error) return null;

  const getErrorIcon = (code) => {
    switch (code) {
      case 'access_denied': return '🚫';
      case 'server_error': return '⚠️';
      case 'rate_limit_exceeded': return '⏱️';
      case 'signature_verification_failed': return '🔐';
      case 'backup_decryption_failed': return '🔑';
      default: return '❌';
    }
  };

  const getErrorColor = (code) => {
    switch (code) {
      case 'access_denied': return 'yellow';
      case 'server_error': return 'red';
      case 'rate_limit_exceeded': return 'orange';
      default: return 'red';
    }
  };

  return (
    <div className={`bg-${getErrorColor(error.code)}-50 border border-${getErrorColor(error.code)}-200 rounded-lg p-4 mb-4`}>
      <div className="flex items-start">
        <span className="text-2xl mr-3">{getErrorIcon(error.code)}</span>
        <div className="flex-1">
          <h3 className="font-semibold text-gray-900 mb-1">
            Authentication Error
          </h3>
          <p className="text-gray-700 mb-3">
            {error.userMessage}
          </p>
          
          {error.code === 'rate_limit_exceeded' && (
            <p className="text-sm text-gray-600 mb-3">
              Please wait before trying again. Rate limits help keep the service secure.
            </p>
          )}
          
          <div className="flex gap-2">
            <button
              onClick={onRetry}
              className="bg-blue-600 text-white px-4 py-2 rounded text-sm hover:bg-blue-700"
            >
              Try Again
            </button>
            <button
              onClick={onClear}
              className="bg-gray-300 text-gray-700 px-4 py-2 rounded text-sm hover:bg-gray-400"
            >
              Dismiss
            </button>
          </div>
          
          {process.env.NODE_ENV === 'development' && (
            <details className="mt-4">
              <summary className="text-sm text-gray-500 cursor-pointer">
                Debug Information
              </summary>
              <pre className="text-xs bg-gray-100 p-2 mt-2 rounded overflow-auto">
                {JSON.stringify(error, null, 2)}
              </pre>
            </details>
          )}
        </div>
      </div>
    </div>
  );
}

URL Parameter Error Handling

Handle errors passed via URL parameters after redirects:

function handleAuthCallback() {
  const urlParams = new URLSearchParams(window.location.search);
  const error = urlParams.get('error');
  const errorDescription = urlParams.get('error_description');
  const state = urlParams.get('state');
  const requestId = urlParams.get('request_id');

  if (error) {
    // Verify state parameter
    const savedState = sessionStorage.getItem('oauth_state');
    if (state !== savedState) {
      console.error('State mismatch - possible CSRF attack');
      return {
        error: 'state_mismatch',
        description: 'Invalid state parameter',
        userMessage: 'Security error. Please try signing in again.'
      };
    }

    // Handle specific errors
    switch (error) {
      case 'access_denied':
        // User cancelled - this is normal, just show a message
        return {
          error,
          description: errorDescription,
          userMessage: 'Sign-in was cancelled. You can try again anytime.'
        };

      case 'server_error':
        // Server error - retry might work
        return {
          error,
          description: errorDescription,
          userMessage: 'Service temporarily unavailable. Please try again.',
          canRetry: true
        };

      case 'rate_limit_exceeded':
        // Rate limited - show wait time
        const retryAfter = urlParams.get('retry_after') || 60;
        return {
          error,
          description: errorDescription,
          userMessage: `Too many attempts. Please wait ${retryAfter} seconds.`,
          retryAfter: parseInt(retryAfter)
        };

      default:
        return {
          error,
          description: errorDescription,
          userMessage: getUserFriendlyMessage(error, errorDescription),
          requestId
        };
    }
  }

  // No error, proceed with normal callback handling
  return null;
}

Development vs Production Error Details

Development Mode

Errors include additional debugging information:

{
  "error": "signature_verification_failed",
  "error_description": "Invalid Bitcoin signature for message",
  "details": {
    "pubkey": "02a94e09bcd6085e580f9214d2814e985f348b1b24121b2aff70a169f971ff5699",
    "message": "auth_challenge_1705123456789",
    "signature": "H1234567890abcdef...",
    "expected_message_format": "{challenge}:{timestamp}",
    "signature_verification_steps": [
      "Message hash computed",
      "Public key recovered from signature", 
      "Key comparison failed"
    ]
  },
  "request_id": "req_dev_abc123",
  "stack_trace": "Error at signature.verify..."
}

Production Mode

Errors provide minimal information to avoid information disclosure:

{
  "error": "signature_verification_failed",
  "error_description": "Authentication signature is invalid",
  "request_id": "req_prod_xyz789"
}

Request ID Tracking

Every error response includes a unique request_id for tracing:

function reportError(error, requestId) {
  // Send to monitoring service
  fetch('/api/error-reports', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      error_code: error.error,
      error_description: error.error_description,
      request_id: requestId,
      user_agent: navigator.userAgent,
      timestamp: Date.now(),
      url: window.location.href
    })
  });
}

// Include request ID in support requests
function generateSupportInfo(error) {
  return {
    message: "Please include this information when contacting support:",
    request_id: error.request_id,
    timestamp: new Date().toISOString(),
    error_code: error.error
  };
}

Automated Error Recovery

Implement Smart Retry Logic

class AuthErrorRecovery {
  constructor() {
    this.retryAttempts = new Map();
    this.maxRetries = 3;
    this.retryDelay = 1000; // Start with 1 second
  }

  async handleErrorWithRecovery(error, operation) {
    const key = operation.name || 'default';
    const attempts = this.retryAttempts.get(key) || 0;

    // Check if error is retryable
    if (!this.isRetryable(error) || attempts >= this.maxRetries) {
      throw error;
    }

    // Increment attempt counter
    this.retryAttempts.set(key, attempts + 1);

    // Calculate delay with exponential backoff
    const delay = this.retryDelay * Math.pow(2, attempts);
    
    console.log(`Retrying ${key} in ${delay}ms (attempt ${attempts + 1}/${this.maxRetries})`);
    await new Promise(resolve => setTimeout(resolve, delay));

    try {
      const result = await operation();
      // Success - reset counter
      this.retryAttempts.delete(key);
      return result;
    } catch (retryError) {
      return this.handleErrorWithRecovery(retryError, operation);
    }
  }

  isRetryable(error) {
    const retryableErrors = [
      'server_error',
      'temporarily_unavailable',
      'service_unavailable'
    ];

    return retryableErrors.includes(error.error) ||
           error.message?.includes('network') ||
           error.message?.includes('timeout');
  }

  reset() {
    this.retryAttempts.clear();
  }
}

// Usage
const recovery = new AuthErrorRecovery();

async function authenticateWithRecovery() {
  return recovery.handleErrorWithRecovery(
    { error: 'unknown' },
    async () => {
      const response = await fetch('/api/auth/token', {
        method: 'POST',
        body: formData
      });
      
      if (!response.ok) {
        const error = await response.json();
        throw error;
      }
      
      return response.json();
    }
  );
}

Error Monitoring and Analytics

Track Error Patterns

class AuthErrorTracker {
  constructor(analyticsProvider) {
    this.analytics = analyticsProvider;
    this.errorCounts = new Map();
  }

  trackError(error, context = {}) {
    // Increment local counter
    const key = error.error;
    this.errorCounts.set(key, (this.errorCounts.get(key) || 0) + 1);

    // Send to analytics
    this.analytics.track('auth_error', {
      error_code: error.error,
      error_description: error.error_description,
      request_id: error.request_id,
      user_agent: navigator.userAgent,
      page_url: window.location.href,
      ...context
    });

    // Alert if error rate is high
    if (this.errorCounts.get(key) > 5) {
      console.warn(`High error rate for ${key}: ${this.errorCounts.get(key)} occurrences`);
      this.analytics.track('auth_error_spike', { error_code: key });
    }
  }

  getErrorStats() {
    return Object.fromEntries(this.errorCounts);
  }
}

// Usage
const errorTracker = new AuthErrorTracker(window.gtag || console);

// In your error handler
if (authError) {
  errorTracker.trackError(authError, {
    flow_type: 'oauth',
    provider: 'sigma',
    client_id: 'your-app'
  });
}

Proper error handling is crucial for a good authentication experience. By implementing comprehensive error detection, user-friendly messages, smart retry logic, and proper monitoring, you can ensure your OAuth integration is robust and provides helpful feedback to both users and developers.