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 codeerror_description
: Human-readable error descriptionerror_uri
: Link to documentation for this error typestate
: 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
, orresponse_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.