Setup
Vanilla JavaScript Implementation
Complete examples for integrating Sigma Auth with vanilla JavaScript applications.
Basic OAuth Flow
HTML Setup
<!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
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:
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:
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:
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.