Sigma Auth
Setup

Vanilla JavaScript Implementation

Complete examples for integrating Sigma Auth with vanilla JavaScript applications.

Basic OAuth Flow

HTML Setup

index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Sigma Auth Example</title>
</head>
<body>
    <div id="app">
        <div id="login-section">
            <h1>Welcome to My App</h1>
            <button id="signin-btn">Sign In with Bitcoin</button>
            <button id="signin-google-btn">Sign In with Google</button>
            <button id="signin-github-btn">Sign In with GitHub</button>
        </div>
        
        <div id="user-section" style="display: none;">
            <h1>Welcome!</h1>
            <div id="user-info"></div>
            <button id="signout-btn">Sign Out</button>
        </div>
        
        <div id="loading" style="display: none;">
            <p>Authenticating...</p>
        </div>
        
        <div id="error" style="display: none;">
            <p id="error-message"></p>
            <button id="retry-btn">Retry</button>
        </div>
    </div>
    
    <script src="sigma-auth.js"></script>
</body>
</html>

JavaScript Implementation

sigma-auth.js
// sigma-auth.js
class SigmaAuthClient {
    constructor(config) {
        this.baseUrl = config.baseUrl || 'https://auth.sigmaidentity.com';
        this.clientId = config.clientId;
        this.redirectUri = config.redirectUri || window.location.origin + '/callback';
        this.scope = config.scope || 'openid profile';
        
        this.init();
    }
    
    init() {
        // Bind event listeners
        document.getElementById('signin-btn')?.addEventListener('click', () => {
            this.signIn('sigma');
        });
        
        document.getElementById('signin-google-btn')?.addEventListener('click', () => {
            this.signIn('google');
        });
        
        document.getElementById('signin-github-btn')?.addEventListener('click', () => {
            this.signIn('github');
        });
        
        document.getElementById('signout-btn')?.addEventListener('click', () => {
            this.signOut();
        });
        
        document.getElementById('retry-btn')?.addEventListener('click', () => {
            this.hideError();
            this.checkAuthStatus();
        });
        
        // Check for OAuth callback
        this.handleCallback();
        
        // Check existing auth status
        this.checkAuthStatus();
    }
    
    async signIn(provider = 'sigma') {
        try {
            this.showLoading();
            
            // Generate PKCE parameters
            const { codeVerifier, codeChallenge } = await this.generatePKCE();
            
            // Store code verifier for token exchange
            sessionStorage.setItem('code_verifier', codeVerifier);
            
            // Generate state for CSRF protection
            const state = this.generateState();
            sessionStorage.setItem('oauth_state', state);
            
            // Build authorization URL
            const authUrl = new URL('/authorize', this.baseUrl);
            authUrl.searchParams.set('client_id', this.clientId);
            authUrl.searchParams.set('redirect_uri', this.redirectUri);
            authUrl.searchParams.set('response_type', 'code');
            authUrl.searchParams.set('provider', provider);
            authUrl.searchParams.set('scope', this.scope);
            authUrl.searchParams.set('state', state);
            authUrl.searchParams.set('code_challenge', codeChallenge);
            authUrl.searchParams.set('code_challenge_method', 'S256');
            
            // Redirect to authorization server
            window.location.href = authUrl.toString();
        } catch (error) {
            this.showError('Failed to initiate sign in: ' + error.message);
        }
    }
    
    async handleCallback() {
        const urlParams = new URLSearchParams(window.location.search);
        const code = urlParams.get('code');
        const state = urlParams.get('state');
        const error = urlParams.get('error');
        
        if (error) {
            this.showError(`Authentication failed: ${error}`);
            return;
        }
        
        if (code) {
            try {
                this.showLoading();
                
                // Verify state parameter
                const storedState = sessionStorage.getItem('oauth_state');
                if (state !== storedState) {
                    throw new Error('Invalid state parameter');
                }
                
                // Exchange code for token
                await this.exchangeCodeForToken(code);
                
                // Clean up URL
                window.history.replaceState({}, document.title, window.location.pathname);
            } catch (error) {
                this.showError('Authentication failed: ' + error.message);
            }
        }
    }
    
    async exchangeCodeForToken(code) {
        const codeVerifier = sessionStorage.getItem('code_verifier');
        
        const response = await fetch(`${this.baseUrl}/token`, {
            method: 'POST',
            headers: {
                'Content-Type': 'application/x-www-form-urlencoded',
            },
            body: new URLSearchParams({
                grant_type: 'authorization_code',
                code: code,
                client_id: this.clientId,
                redirect_uri: this.redirectUri,
                code_verifier: codeVerifier
            })
        });
        
        if (!response.ok) {
            const error = await response.json();
            throw new Error(error.error_description || 'Token exchange failed');
        }
        
        const tokens = await response.json();
        
        // Store access token
        sessionStorage.setItem('access_token', tokens.access_token);
        
        // Clean up temporary data
        sessionStorage.removeItem('code_verifier');
        sessionStorage.removeItem('oauth_state');
        
        // Get user info and update UI
        await this.loadUserInfo();
    }
    
    async loadUserInfo() {
        const accessToken = sessionStorage.getItem('access_token');
        
        if (!accessToken) {
            this.showLogin();
            return;
        }
        
        try {
            const response = await fetch(`${this.baseUrl}/userinfo`, {
                headers: {
                    'Authorization': `Bearer ${accessToken}`
                }
            });
            
            if (!response.ok) {
                if (response.status === 401) {
                    // Token expired
                    this.signOut();
                    return;
                }
                throw new Error('Failed to load user info');
            }
            
            const user = await response.json();
            this.showUserInfo(user);
        } catch (error) {
            this.showError('Failed to load user info: ' + error.message);
        }
    }
    
    signOut() {
        // Clear stored tokens
        sessionStorage.removeItem('access_token');
        sessionStorage.removeItem('code_verifier');
        sessionStorage.removeItem('oauth_state');
        
        this.showLogin();
    }
    
    checkAuthStatus() {
        const accessToken = sessionStorage.getItem('access_token');
        
        if (accessToken) {
            this.loadUserInfo();
        } else {
            this.showLogin();
        }
    }
    
    // UI Helper Methods
    showLogin() {
        this.hideAll();
        document.getElementById('login-section').style.display = 'block';
    }
    
    showUserInfo(user) {
        this.hideAll();
        
        const userSection = document.getElementById('user-section');
        const userInfo = document.getElementById('user-info');
        
        userInfo.innerHTML = `
            <div class="user-card">
                <img src="${user.picture || '/default-avatar.png'}" alt="Avatar" width="80" height="80">
                <h3>${user.name || 'Anonymous'}</h3>
                <p><strong>Address:</strong> ${user.address}</p>
                <p><strong>Email:</strong> ${user.email || 'Not provided'}</p>
                ${user.profile?.bio ? `<p><strong>Bio:</strong> ${user.profile.bio}</p>` : ''}
            </div>
        `;
        
        userSection.style.display = 'block';
    }
    
    showLoading() {
        this.hideAll();
        document.getElementById('loading').style.display = 'block';
    }
    
    showError(message) {
        this.hideAll();
        document.getElementById('error-message').textContent = message;
        document.getElementById('error').style.display = 'block';
    }
    
    hideError() {
        document.getElementById('error').style.display = 'none';
    }
    
    hideAll() {
        const sections = ['login-section', 'user-section', 'loading', 'error'];
        sections.forEach(id => {
            document.getElementById(id).style.display = 'none';
        });
    }
    
    // Utility Methods
    async generatePKCE() {
        const codeVerifier = this.base64URLEncode(crypto.getRandomValues(new Uint8Array(32)));
        const encoder = new TextEncoder();
        const data = encoder.encode(codeVerifier);
        const digest = await crypto.subtle.digest('SHA-256', data);
        const codeChallenge = this.base64URLEncode(new Uint8Array(digest));
        
        return { codeVerifier, codeChallenge };
    }
    
    generateState() {
        return this.base64URLEncode(crypto.getRandomValues(new Uint8Array(32)));
    }
    
    base64URLEncode(array) {
        return btoa(String.fromCharCode(...array))
            .replace(/\+/g, '-')
            .replace(/\//g, '_')
            .replace(/=/g, '');
    }
}

// Initialize the client
const authClient = new SigmaAuthClient({
    clientId: 'your-app-name',
    baseUrl: 'https://auth.sigmaidentity.com',
    redirectUri: window.location.origin,
    scope: 'openid profile email'
});

Direct Bitcoin Authentication

For applications that want to handle Bitcoin signatures directly:

bitcoin-auth-direct.js
class BitcoinAuthClient {
    constructor(config) {
        this.baseUrl = config.baseUrl || 'https://auth.sigmaidentity.com';
        this.clientId = config.clientId;
        
        this.privateKey = null;
        this.publicKey = null;
        this.address = null;
    }
    
    async generateKey() {
        // Note: In production, use a proper Bitcoin library like @bsv/sdk
        // This is a simplified example
        
        try {
            // Generate random private key (32 bytes)
            const privateKeyBytes = crypto.getRandomValues(new Uint8Array(32));
            this.privateKey = this.bytesToHex(privateKeyBytes);
            
            // Derive public key and address
            // (In production, use proper secp256k1 library)
            this.publicKey = await this.derivePublicKey(this.privateKey);
            this.address = await this.deriveAddress(this.publicKey);
            
            // Store securely (don't use localStorage in production!)
            sessionStorage.setItem('bitcoin_private_key', this.privateKey);
            
            return {
                privateKey: this.privateKey,
                publicKey: this.publicKey,
                address: this.address
            };
        } catch (error) {
            throw new Error('Failed to generate Bitcoin key: ' + error.message);
        }
    }
    
    async signMessage(message) {
        if (!this.privateKey) {
            throw new Error('No private key available');
        }
        
        // In production, use proper ECDSA signing
        // This is a placeholder implementation
        const signature = await this.ecdsaSign(message, this.privateKey);
        return signature;
    }
    
    async authenticate() {
        if (!this.privateKey) {
            throw new Error('No private key available. Generate one first.');
        }
        
        try {
            // Create authentication payload
            const timestamp = new Date().toISOString();
            const requestPath = '/sigma/authorize';
            
            const authPayload = {
                address: this.address,
                publicKey: this.publicKey,
                timestamp: timestamp,
                requestPath: requestPath,
                clientId: this.clientId
            };
            
            // Sign the payload
            const message = JSON.stringify(authPayload);
            const signature = await this.signMessage(message);
            
            // Create auth token
            const authToken = `bitcoin:${this.address}:${signature}`;
            
            // Make authentication request
            const response = await fetch(`${this.baseUrl}/sigma/authorize`, {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                    'X-Auth-Token': authToken
                },
                body: JSON.stringify({
                    client_id: this.clientId
                })
            });
            
            if (!response.ok) {
                const error = await response.json();
                throw new Error(error.error_description || 'Authentication failed');
            }
            
            const result = await response.json();
            return result;
        } catch (error) {
            throw new Error('Bitcoin authentication failed: ' + error.message);
        }
    }
    
    // Utility methods (simplified - use proper crypto libraries in production)
    bytesToHex(bytes) {
        return Array.from(bytes, byte => byte.toString(16).padStart(2, '0')).join('');
    }
    
    async derivePublicKey(privateKeyHex) {
        // Placeholder - use proper secp256k1 implementation
        return 'derived_public_key_placeholder';
    }
    
    async deriveAddress(publicKeyHex) {
        // Placeholder - use proper Bitcoin address derivation
        return '1ExampleBitcoinAddress123456789';
    }
    
    async ecdsaSign(message, privateKeyHex) {
        // Placeholder - use proper ECDSA signing
        return 'ecdsa_signature_placeholder';
    }
}

// Usage example
const bitcoinAuth = new BitcoinAuthClient({
    clientId: 'your-app-name',
    baseUrl: 'https://auth.sigmaidentity.com'
});

// Generate key and authenticate
document.getElementById('generate-key-btn').addEventListener('click', async () => {
    try {
        const keyInfo = await bitcoinAuth.generateKey();
        console.log('Generated key:', keyInfo);
        
        // Enable authentication button
        document.getElementById('auth-btn').disabled = false;
    } catch (error) {
        console.error('Key generation failed:', error);
    }
});

document.getElementById('auth-btn').addEventListener('click', async () => {
    try {
        const result = await bitcoinAuth.authenticate();
        console.log('Authentication successful:', result);
        
        // Handle successful authentication
        displayUserInfo(result.user);
    } catch (error) {
        console.error('Authentication failed:', error);
        displayError(error.message);
    }
});

Protected API Calls

Example of making authenticated requests to protected endpoints:

protected-api-client.js
class ProtectedAPIClient {
    constructor() {
        this.baseUrl = 'https://your-api.example.com';
    }
    
    async makeAuthenticatedRequest(endpoint, options = {}) {
        const accessToken = sessionStorage.getItem('access_token');
        
        if (!accessToken) {
            throw new Error('No access token available');
        }
        
        const headers = {
            'Authorization': `Bearer ${accessToken}`,
            'Content-Type': 'application/json',
            ...options.headers
        };
        
        const response = await fetch(`${this.baseUrl}${endpoint}`, {
            ...options,
            headers
        });
        
        if (response.status === 401) {
            // Token expired - redirect to login
            sessionStorage.removeItem('access_token');
            window.location.href = '/login';
            return;
        }
        
        if (!response.ok) {
            const error = await response.json();
            throw new Error(error.message || 'API request failed');
        }
        
        return response.json();
    }
    
    async getUserProfile() {
        return this.makeAuthenticatedRequest('/api/user/profile');
    }
    
    async updateUserProfile(profileData) {
        return this.makeAuthenticatedRequest('/api/user/profile', {
            method: 'PUT',
            body: JSON.stringify(profileData)
        });
    }
    
    async getUserPosts() {
        return this.makeAuthenticatedRequest('/api/user/posts');
    }
}

// Usage
const apiClient = new ProtectedAPIClient();

// Load user data on page load
document.addEventListener('DOMContentLoaded', async () => {
    try {
        const profile = await apiClient.getUserProfile();
        displayUserProfile(profile);
        
        const posts = await apiClient.getUserPosts();
        displayUserPosts(posts);
    } catch (error) {
        console.error('Failed to load user data:', error);
    }
});

Error Handling

Comprehensive error handling for production applications:

sigma-auth-error-handler.js
class SigmaAuthErrorHandler {
    static handle(error, context = {}) {
        console.error('Sigma Auth Error:', error, context);
        
        // Log error to monitoring service
        this.logError(error, context);
        
        // Show user-friendly error message
        this.showUserError(error);
        
        // Handle specific error types
        switch (error.name) {
            case 'NetworkError':
                this.handleNetworkError(error);
                break;
            case 'AuthenticationError':
                this.handleAuthError(error);
                break;
            case 'ValidationError':
                this.handleValidationError(error);
                break;
            default:
                this.handleGenericError(error);
        }
    }
    
    static handleNetworkError(error) {
        // Show retry button
        this.showRetryOption(() => {
            // Retry the failed operation
            window.location.reload();
        });
    }
    
    static handleAuthError(error) {
        // Clear any stored tokens
        sessionStorage.clear();
        
        // Redirect to login
        setTimeout(() => {
            window.location.href = '/login';
        }, 2000);
    }
    
    static handleValidationError(error) {
        // Highlight invalid form fields
        const fieldName = error.field;
        if (fieldName) {
            const field = document.querySelector(`[name="${fieldName}"]`);
            if (field) {
                field.classList.add('error');
                field.focus();
            }
        }
    }
    
    static handleGenericError(error) {
        // Show generic error message
        this.showNotification('An unexpected error occurred. Please try again.', 'error');
    }
    
    static logError(error, context) {
        // Send to logging service (e.g., Sentry, LogRocket)
        if (window.Sentry) {
            window.Sentry.captureException(error, { extra: context });
        }
    }
    
    static showUserError(error) {
        const errorContainer = document.getElementById('error-container');
        if (errorContainer) {
            errorContainer.innerHTML = `
                <div class="error-message">
                    <strong>Error:</strong> ${this.getUserFriendlyMessage(error)}
                </div>
            `;
            errorContainer.style.display = 'block';
        }
    }
    
    static getUserFriendlyMessage(error) {
        const errorMap = {
            'invalid_grant': 'Your session has expired. Please sign in again.',
            'access_denied': 'Access was denied. Please try signing in again.',
            'invalid_signature': 'Authentication failed. Please check your Bitcoin key.',
            'rate_limit_exceeded': 'Too many requests. Please wait a moment and try again.',
            'server_error': 'Our servers are experiencing issues. Please try again later.'
        };
        
        return errorMap[error.error] || error.message || 'An unexpected error occurred.';
    }
    
    static showRetryOption(retryCallback) {
        const retryButton = document.createElement('button');
        retryButton.textContent = 'Retry';
        retryButton.onclick = retryCallback;
        
        const errorContainer = document.getElementById('error-container');
        if (errorContainer) {
            errorContainer.appendChild(retryButton);
        }
    }
    
    static showNotification(message, type = 'info') {
        // Create notification element
        const notification = document.createElement('div');
        notification.className = `notification notification-${type}`;
        notification.textContent = message;
        
        // Add to page
        document.body.appendChild(notification);
        
        // Auto-remove after 5 seconds
        setTimeout(() => {
            notification.remove();
        }, 5000);
    }
}

// Use error handler globally
window.addEventListener('unhandledrejection', event => {
    SigmaAuthErrorHandler.handle(event.reason, { type: 'unhandled_rejection' });
});

window.addEventListener('error', event => {
    SigmaAuthErrorHandler.handle(event.error, { type: 'uncaught_exception' });
});

CSS Styling

Basic styling for the authentication UI:

/* sigma-auth.css */
.user-card {
    border: 1px solid #ddd;
    border-radius: 8px;
    padding: 20px;
    text-align: center;
    max-width: 400px;
    margin: 0 auto;
}

.user-card img {
    border-radius: 50%;
    margin-bottom: 15px;
}

.user-card h3 {
    margin: 0 0 10px 0;
    color: #333;
}

.user-card p {
    margin: 5px 0;
    color: #666;
    word-break: break-all;
}

.error-message {
    background-color: #fee;
    border: 1px solid #fcc;
    border-radius: 4px;
    padding: 10px;
    margin: 10px 0;
    color: #c33;
}

.notification {
    position: fixed;
    top: 20px;
    right: 20px;
    padding: 15px 20px;
    border-radius: 4px;
    color: white;
    font-weight: 500;
    z-index: 1000;
}

.notification-info {
    background-color: #007bff;
}

.notification-error {
    background-color: #dc3545;
}

.notification-success {
    background-color: #28a745;
}

button {
    background-color: #007bff;
    color: white;
    border: none;
    padding: 12px 24px;
    border-radius: 4px;
    cursor: pointer;
    font-size: 16px;
    margin: 5px;
}

button:hover {
    background-color: #0056b3;
}

button:disabled {
    background-color: #6c757d;
    cursor: not-allowed;
}

#loading {
    text-align: center;
    font-size: 18px;
    color: #666;
}

This comprehensive vanilla JavaScript implementation provides a solid foundation for integrating Sigma Auth into any web application. Remember to use proper Bitcoin libraries like @bsv/sdk for production implementations.