Sigma Auth v2 introduces production-ready features including PKCE support, comprehensive rate limiting, backend authentication tokens, and enhanced error handling. This guide helps you upgrade from v1 while maintaining backward compatibility.
What's New in v2
🔐 PKCE Support
- Proof Key for Code Exchange for enhanced security
- Required for SPAs and mobile applications
- Backward compatible with existing OAuth flows
⚡ Rate Limiting
- Comprehensive protection against abuse
- Smart retry logic with proper HTTP headers
- Environment-specific limits for development vs production
🛡️ Backend Auth Tokens
- Direct API authentication for backend services
- Bitcoin-signed tokens without OAuth flow
- Server-to-server communication support
🚨 Enhanced Error Handling
- Detailed error responses with request IDs
- OAuth 2.0 compliant error formats
- Development-friendly debugging information
Breaking Changes (Minimal)
1. Error Response Format
Error responses now include additional fields for better debugging:
v1 Format:
{
"error": "invalid_request",
"error_description": "Missing client_id"
}
v2 Format:
{
"error": "invalid_request",
"error_description": "Missing required parameter: client_id",
"error_uri": "https://docs.sigmaidentity.com/docs/oauth/error-handling#invalid_request",
"request_id": "req_abc123"
}
Migration: Update error handling to work with both formats:
// v2 compatible error handling
function handleAuthError(error) {
const errorCode = error.error;
const description = error.error_description;
const requestId = error.request_id; // New in v2
console.error(`Auth error [${errorCode}]:`, description);
if (requestId) {
console.log(`Request ID for support: ${requestId}`);
}
// Your existing error handling logic
showUserError(description);
}
2. Rate Limit Headers
Response headers now include rate limit information:
// v2: Check rate limit headers
function checkRateLimit(response) {
const remaining = response.headers.get('X-RateLimit-Remaining');
const reset = response.headers.get('X-RateLimit-Reset');
if (remaining && parseInt(remaining) < 10) {
console.warn(`Rate limit low: ${remaining} requests remaining`);
}
return response;
}
// Apply to all auth requests
fetch('/api/auth/token', options)
.then(checkRateLimit)
.then(response => response.json());
New Features Implementation
1. Implementing PKCE (Recommended)
PKCE significantly improves security with minimal code changes:
Before (v1 OAuth Flow):
function startLogin() {
const state = generateState();
sessionStorage.setItem('oauth_state', state);
const params = new URLSearchParams({
client_id: 'your-app',
redirect_uri: 'https://yourapp.com/callback',
response_type: 'code',
state: state
});
window.location.href = `https://auth.sigmaidentity.com/authorize?${params}`;
}
After (v2 with PKCE):
async function startLoginWithPKCE() {
const state = generateState();
const codeVerifier = generateCodeVerifier();
const codeChallenge = await generateCodeChallenge(codeVerifier);
// Store for later use
sessionStorage.setItem('oauth_state', state);
sessionStorage.setItem('pkce_verifier', codeVerifier);
const params = new URLSearchParams({
client_id: 'your-app',
redirect_uri: 'https://yourapp.com/callback',
response_type: 'code',
state: state,
// PKCE parameters (new)
code_challenge: codeChallenge,
code_challenge_method: 'S256'
});
window.location.href = `https://auth.sigmaidentity.com/authorize?${params}`;
}
// Helper functions for PKCE
function generateCodeVerifier() {
const array = new Uint8Array(32);
crypto.getRandomValues(array);
return base64urlEncode(array);
}
async function generateCodeChallenge(verifier) {
const encoder = new TextEncoder();
const data = encoder.encode(verifier);
const digest = await crypto.subtle.digest('SHA-256', data);
return base64urlEncode(digest);
}
function base64urlEncode(buffer) {
return btoa(String.fromCharCode(...new Uint8Array(buffer)))
.replace(/=/g, '')
.replace(/\+/g, '-')
.replace(/\//g, '_');
}
Update Token Exchange:
// v1 token exchange
async function exchangeToken(code) {
const response = await fetch('https://auth.sigmaidentity.com/token', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
grant_type: 'authorization_code',
code: code,
client_id: 'your-app',
redirect_uri: 'https://yourapp.com/callback'
})
});
return response.json();
}
// v2 token exchange with PKCE
async function exchangeTokenWithPKCE(code) {
const codeVerifier = sessionStorage.getItem('pkce_verifier');
const response = await fetch('https://auth.sigmaidentity.com/token', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
grant_type: 'authorization_code',
code: code,
client_id: 'your-app',
redirect_uri: 'https://yourapp.com/callback',
// PKCE verification (new)
code_verifier: codeVerifier
})
});
// Clean up stored verifier
sessionStorage.removeItem('pkce_verifier');
return response.json();
}
2. Rate Limit Handling
Implement proper rate limit handling for a better user experience:
class RateLimitHandler {
constructor() {
this.retryQueue = new Map();
}
async makeRequest(url, options) {
try {
const response = await fetch(url, options);
// Check rate limit headers
const remaining = parseInt(response.headers.get('X-RateLimit-Remaining') || '999');
const reset = parseInt(response.headers.get('X-RateLimit-Reset') || '0');
if (response.status === 429) {
const retryAfter = parseInt(response.headers.get('X-RateLimit-Retry-After') || '60');
throw new RateLimitError(retryAfter);
}
// Warn when rate limit is low
if (remaining < 10) {
console.warn(`Rate limit low: ${remaining} requests remaining until ${new Date(reset * 1000)}`);
}
return response;
} catch (error) {
if (error instanceof RateLimitError) {
return this.handleRateLimit(url, options, error.retryAfter);
}
throw error;
}
}
async handleRateLimit(url, options, retryAfter) {
console.log(`Rate limited. Retrying after ${retryAfter} seconds...`);
await new Promise(resolve => setTimeout(resolve, retryAfter * 1000));
return this.makeRequest(url, options);
}
}
class RateLimitError extends Error {
constructor(retryAfter) {
super(`Rate limited for ${retryAfter} seconds`);
this.retryAfter = retryAfter;
}
}
// Usage
const rateLimitHandler = new RateLimitHandler();
// Replace direct fetch calls
const response = await rateLimitHandler.makeRequest('/api/auth/token', {
method: 'POST',
body: formData
});
3. Backend Authentication Tokens
For backend services, use the new direct token endpoint:
import { PrivateKey } from '@bsv/sdk';
import { SignedMessage } from 'bitcoin-auth';
class BackendAuthService {
constructor(serviceWif) {
this.privateKey = PrivateKey.fromWif(serviceWif);
this.cachedToken = null;
this.tokenExpiry = null;
}
async getToken(endpoint) {
// Return cached token if valid
if (this.cachedToken && this.tokenExpiry > Date.now() + 60000) {
return this.cachedToken;
}
// Generate signature
const timestamp = Date.now();
const message = `${endpoint}:${timestamp}`;
const signature = SignedMessage.sign(message, this.privateKey);
// Request token
const response = await fetch('https://auth.sigmaidentity.com/api/auth/token-for-endpoint', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
endpoint,
signature,
message,
pubkey: this.privateKey.toPublicKey().toString(),
timestamp
})
});
const { access_token, expires_in } = await response.json();
// Cache token
this.cachedToken = access_token;
this.tokenExpiry = Date.now() + (expires_in * 1000) - 60000;
return access_token;
}
async authenticatedRequest(url, options = {}) {
const token = await this.getToken(url);
return fetch(url, {
...options,
headers: {
'Authorization': `Bearer ${token}`,
...options.headers
}
});
}
}
// Usage
const backendAuth = new BackendAuthService(process.env.SERVICE_ACCOUNT_WIF);
const response = await backendAuth.authenticatedRequest('https://api.yourservice.com/data');
4. Enhanced Error Handling
Implement comprehensive error handling:
class AuthErrorHandler {
handleError(error) {
const errorInfo = {
code: error.error,
description: error.error_description,
requestId: error.request_id,
timestamp: new Date()
};
// Log with request ID for support
console.error(`Auth Error [${errorInfo.code}]:`, errorInfo.description);
if (errorInfo.requestId) {
console.log(`Request ID: ${errorInfo.requestId}`);
}
// Handle specific error types
switch (error.error) {
case 'rate_limit_exceeded':
const retryAfter = error.retry_after || 60;
return this.handleRateLimit(retryAfter);
case 'signature_verification_failed':
return this.handleSignatureError();
case 'invalid_grant':
return this.handleExpiredCode();
case 'server_error':
return this.handleServerError(errorInfo);
default:
return this.handleGenericError(errorInfo);
}
}
handleRateLimit(retryAfter) {
return {
type: 'rate_limit',
message: `Please wait ${retryAfter} seconds before trying again.`,
canRetry: true,
retryAfter
};
}
handleSignatureError() {
return {
type: 'signature_error',
message: 'Bitcoin signature verification failed. Please check your backup file.',
canRetry: true,
action: 'reload_backup'
};
}
handleExpiredCode() {
return {
type: 'expired_code',
message: 'Authentication session expired. Please sign in again.',
canRetry: true,
action: 'restart_auth'
};
}
handleServerError(errorInfo) {
return {
type: 'server_error',
message: 'Service temporarily unavailable. Please try again.',
canRetry: true,
supportInfo: {
requestId: errorInfo.requestId,
timestamp: errorInfo.timestamp
}
};
}
handleGenericError(errorInfo) {
return {
type: 'generic_error',
message: errorInfo.description || 'An authentication error occurred.',
canRetry: false,
supportInfo: {
requestId: errorInfo.requestId,
code: errorInfo.code
}
};
}
}
// Usage
const errorHandler = new AuthErrorHandler();
try {
const result = await authenticateUser();
} catch (error) {
const handledError = errorHandler.handleError(error);
showErrorToUser(handledError);
}
Testing Your v2 Integration
1. Feature Detection
Test if v2 features are available:
async function detectV2Features() {
try {
// Test PKCE support
const pkceResponse = await fetch('https://auth.sigmaidentity.com/authorize?code_challenge_method=S256&code_challenge=test');
const supportsPKCE = !pkceResponse.url.includes('unsupported_response_type');
// Test rate limit headers
const rateLimitResponse = await fetch('https://auth.sigmaidentity.com/health');
const supportsRateLimit = rateLimitResponse.headers.has('X-RateLimit-Limit');
// Test backend tokens
const backendTokenResponse = await fetch('https://auth.sigmaidentity.com/api/auth/token-for-endpoint', {
method: 'POST'
});
const supportsBackendTokens = backendTokenResponse.status !== 404;
return {
pkce: supportsPKCE,
rateLimit: supportsRateLimit,
backendTokens: supportsBackendTokens
};
} catch (error) {
console.warn('Feature detection failed:', error);
return { pkce: false, rateLimit: false, backendTokens: false };
}
}
// Use feature detection
const features = await detectV2Features();
if (features.pkce) {
// Use PKCE flow
} else {
// Fallback to basic OAuth
}
2. Compatibility Testing
Test both v1 and v2 flows:
describe('Sigma Auth v2 Compatibility', () => {
it('should work with v1 OAuth flow', async () => {
const code = await performBasicOAuthFlow();
expect(code).toBeDefined();
});
it('should work with v2 PKCE flow', async () => {
const code = await performPKCEOAuthFlow();
expect(code).toBeDefined();
});
it('should handle rate limits gracefully', async () => {
// Make multiple requests to trigger rate limit
const responses = await Promise.all([
...Array(15).fill().map(() => fetch('/api/auth/token'))
]);
const rateLimited = responses.some(r => r.status === 429);
expect(rateLimited).toBe(true);
});
it('should include error request IDs', async () => {
try {
await fetch('/api/auth/token', { method: 'POST' }); // Invalid request
} catch (error) {
expect(error.request_id).toBeDefined();
}
});
});
Rollback Plan
If you encounter issues with v2, you can temporarily rollback:
1. Environment Variables
# Disable v2 features temporarily
DISABLE_PKCE=true
DISABLE_RATE_LIMITING=true
DISABLE_ENHANCED_ERRORS=true
2. Client-Side Fallback
// Graceful degradation to v1 behavior
function createAuthUrl(usePKCE = true) {
const baseParams = {
client_id: 'your-app',
redirect_uri: 'https://yourapp.com/callback',
response_type: 'code',
state: generateState()
};
if (usePKCE) {
try {
const codeVerifier = generateCodeVerifier();
const codeChallenge = await generateCodeChallenge(codeVerifier);
sessionStorage.setItem('pkce_verifier', codeVerifier);
return new URLSearchParams({
...baseParams,
code_challenge: codeChallenge,
code_challenge_method: 'S256'
});
} catch (error) {
console.warn('PKCE failed, falling back to basic OAuth:', error);
// Fall through to basic OAuth
}
}
return new URLSearchParams(baseParams);
}
Timeline and Support
Rollout Schedule
- January 2024: v2 available in development
- February 2024: v2 available in staging
- March 2024: v2 promoted to production
- June 2024: v1 features marked deprecated
- December 2024: v1 support ends
Getting Help
- Documentation: OAuth Features
- GitHub Issues: Report problems
- Email Support: support@sigmaidentity.com (include request ID)
- Migration Assistance: Available for high-volume users
Deprecation Notices
- v1 OAuth flows: Continue to work but are deprecated
- Basic error responses: Will include v2 fields by default
- No rate limit handling: Applications may experience 429 errors
Best Practices for v2
1. Always Use PKCE
// Good: Always include PKCE
const authUrl = await createPKCEAuthUrl();
// Avoid: Plain OAuth (less secure)
const authUrl = createBasicAuthUrl();
2. Handle Rate Limits Gracefully
// Good: Respect rate limits
if (response.status === 429) {
const retryAfter = response.headers.get('X-RateLimit-Retry-After');
await sleep(retryAfter * 1000);
}
// Avoid: Ignoring rate limits
// This will lead to more errors and poor UX
3. Use Request IDs for Support
// Good: Log request IDs for debugging
if (error.request_id) {
console.log(`Support request ID: ${error.request_id}`);
errorReporting.setContext('request_id', error.request_id);
}
// Good: Include in user-facing error messages
showError(`Authentication failed. Reference: ${error.request_id}`);
4. Implement Proper Error Recovery
// Good: Smart error recovery
switch (error.error) {
case 'invalid_grant':
// Restart auth flow
window.location.href = createAuthUrl();
break;
case 'rate_limit_exceeded':
// Wait and retry
await sleep(error.retry_after * 1000);
return retryAuthentication();
}
Sigma Auth v2 provides significant security and reliability improvements while maintaining backward compatibility. The upgrade path is straightforward, and the new features will make your authentication more robust and user-friendly.