The Auth Token API allows your backend services to generate short-lived, Bitcoin-signed authentication tokens without going through the full OAuth flow. This is ideal for server-to-server communication and API integrations.
Overview
Traditional OAuth is designed for user-facing applications, but backend services often need programmatic authentication. Sigma Auth's /api/auth/token-for-endpoint
endpoint allows you to:
- Generate tokens programmatically using Bitcoin signatures
- Authenticate backend services without browser redirects
- Integrate with existing API keys and service accounts
- Maintain security through cryptographic signatures
Authentication Flow
1. Generate Bitcoin Signature
Your backend needs to sign a specific message format:
import { PrivateKey } from '@bsv/sdk';
import { SignedMessage } from 'bitcoin-auth';
async function generateAuthSignature(privateKey, endpoint) {
// Create the message to sign
const timestamp = Date.now();
const message = `${endpoint}:${timestamp}`;
// Sign with Bitcoin private key
const signature = SignedMessage.sign(message, privateKey);
return {
message,
signature,
timestamp,
pubkey: privateKey.toPublicKey().toString()
};
}
// Example usage
const privateKey = PrivateKey.fromWif('your-service-account-wif');
const authData = await generateAuthSignature(privateKey, 'https://api.yourservice.com');
2. Request Authentication Token
Send the signature to Sigma Auth to get an access token:
async function getBackendToken(authData) {
const response = await fetch('https://auth.sigmaidentity.com/api/auth/token-for-endpoint', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
endpoint: 'https://api.yourservice.com',
signature: authData.signature,
message: authData.message,
pubkey: authData.pubkey,
timestamp: authData.timestamp
})
});
if (!response.ok) {
const error = await response.json();
throw new Error(`Token generation failed: ${error.error_description}`);
}
const { access_token, expires_in } = await response.json();
return { accessToken: access_token, expiresIn: expires_in };
}
3. Use Token for API Calls
Use the generated token to authenticate with your backend APIs:
async function makeAuthenticatedAPICall(accessToken, endpoint, data) {
const response = await fetch(endpoint, {
method: 'POST',
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
});
return response;
}
Complete Backend Service Example
Here's a full example of a backend service using auth tokens:
import { PrivateKey } from '@bsv/sdk';
import { SignedMessage } from 'bitcoin-auth';
class BackendAuthService {
constructor(serviceWif, sigmaAuthUrl = 'https://auth.sigmaidentity.com') {
this.privateKey = PrivateKey.fromWif(serviceWif);
this.sigmaAuthUrl = sigmaAuthUrl;
this.cachedToken = null;
this.tokenExpiry = null;
}
async generateSignature(endpoint) {
const timestamp = Date.now();
const message = `${endpoint}:${timestamp}`;
const signature = SignedMessage.sign(message, this.privateKey);
return {
message,
signature,
timestamp,
pubkey: this.privateKey.toPublicKey().toString()
};
}
async getToken(endpoint, forceRefresh = false) {
// Return cached token if still valid
if (!forceRefresh && this.cachedToken && this.tokenExpiry > Date.now() + 60000) {
return this.cachedToken;
}
try {
const authData = await this.generateSignature(endpoint);
const response = await fetch(`${this.sigmaAuthUrl}/api/auth/token-for-endpoint`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
endpoint,
signature: authData.signature,
message: authData.message,
pubkey: authData.pubkey,
timestamp: authData.timestamp
})
});
if (!response.ok) {
const error = await response.json();
throw new Error(`Auth failed: ${error.error_description}`);
}
const { access_token, expires_in } = await response.json();
// Cache token with buffer time
this.cachedToken = access_token;
this.tokenExpiry = Date.now() + (expires_in * 1000) - 60000; // 1 minute buffer
return access_token;
} catch (error) {
console.error('Token generation failed:', error);
throw error;
}
}
async authenticatedRequest(url, options = {}) {
const token = await this.getToken(url);
return fetch(url, {
...options,
headers: {
'Authorization': `Bearer ${token}`,
...options.headers
}
});
}
async authenticatedPost(url, data) {
return this.authenticatedRequest(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
}
async authenticatedGet(url) {
return this.authenticatedRequest(url, { method: 'GET' });
}
}
// Usage
const authService = new BackendAuthService(process.env.SERVICE_ACCOUNT_WIF);
// Make authenticated requests
const response = await authService.authenticatedPost('https://api.yourservice.com/data', {
message: 'Hello from backend service'
});
const data = await response.json();
console.log('API response:', data);
Express.js Middleware
Integrate backend auth tokens with Express.js:
import express from 'express';
import jwt from 'jsonwebtoken';
const app = express();
// Middleware to validate Sigma Auth tokens
function validateSigmaToken(req, res, next) {
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return res.status(401).json({ error: 'Missing or invalid Authorization header' });
}
const token = authHeader.split(' ')[1];
try {
// Validate the JWT token structure (Sigma Auth uses ES256)
const decoded = jwt.decode(token, { complete: true });
if (!decoded || decoded.header.alg !== 'ES256') {
return res.status(401).json({ error: 'Invalid token format' });
}
// You can verify the token signature against Sigma Auth's public keys
// For this example, we'll accept any valid JWT structure
req.user = {
pubkey: decoded.payload.sub, // Bitcoin public key
bapId: decoded.payload.bap_id,
endpoint: decoded.payload.aud // The endpoint this token was issued for
};
next();
} catch (error) {
return res.status(401).json({ error: 'Invalid token' });
}
}
// Protected endpoint
app.get('/api/protected', validateSigmaToken, (req, res) => {
res.json({
message: 'Access granted',
user: req.user,
timestamp: Date.now()
});
});
// Health check (unprotected)
app.get('/health', (req, res) => {
res.json({ status: 'ok' });
});
app.listen(3000, () => {
console.log('Backend service running on port 3000');
});
Token Validation in Your API
Your backend API should validate tokens received from authenticated requests:
import jwt from 'jsonwebtoken';
import { PublicKey } from '@bsv/sdk';
class TokenValidator {
constructor() {
this.sigmaPublicKeys = null;
this.keysFetchTime = 0;
}
async getSigmaPublicKeys() {
// Cache keys for 1 hour
if (this.sigmaPublicKeys && Date.now() - this.keysFetchTime < 3600000) {
return this.sigmaPublicKeys;
}
try {
const response = await fetch('https://auth.sigmaidentity.com/.well-known/jwks.json');
const jwks = await response.json();
this.sigmaPublicKeys = jwks.keys;
this.keysFetchTime = Date.now();
return this.sigmaPublicKeys;
} catch (error) {
throw new Error('Failed to fetch Sigma Auth public keys');
}
}
async validateToken(token, expectedAudience) {
try {
const keys = await this.getSigmaPublicKeys();
const decoded = jwt.decode(token, { complete: true });
if (!decoded) {
throw new Error('Invalid token format');
}
// Find the correct key
const key = keys.find(k => k.kid === decoded.header.kid);
if (!key) {
throw new Error('Unknown token key');
}
// Verify token signature and claims
const verified = jwt.verify(token, key.x5c[0], {
algorithms: ['ES256'],
audience: expectedAudience,
issuer: 'https://auth.sigmaidentity.com'
});
return {
valid: true,
pubkey: verified.sub,
bapId: verified.bap_id,
endpoint: verified.aud,
expiresAt: new Date(verified.exp * 1000)
};
} catch (error) {
return {
valid: false,
error: error.message
};
}
}
}
// Usage in API endpoint
const validator = new TokenValidator();
app.post('/api/secure-operation', async (req, res) => {
const token = req.headers.authorization?.split(' ')[1];
if (!token) {
return res.status(401).json({ error: 'Token required' });
}
const validation = await validator.validateToken(token, 'https://api.yourservice.com');
if (!validation.valid) {
return res.status(401).json({ error: validation.error });
}
// Token is valid, process the request
res.json({
success: true,
user: validation.pubkey,
data: 'Secure operation completed'
});
});
Environment Variables
Set up your backend service with proper environment variables:
# .env file
SERVICE_ACCOUNT_WIF=L5oLkpV3aqBjhki6LmvChTCV6odsp4SXM6FfU2Gppt5kFLaHLuZ9
SIGMA_AUTH_URL=https://auth.sigmaidentity.com
API_BASE_URL=https://api.yourservice.com
# For development
# SERVICE_ACCOUNT_WIF=your-dev-wif
# SIGMA_AUTH_URL=http://localhost:1911
Security Considerations
Service Account Management
- Rotate WIFs regularly: Generate new service account keys periodically
- Secure storage: Use environment variables or secure key management systems
- Separate keys per service: Don't reuse keys across different backend services
// Key rotation example
class RotatingAuthService extends BackendAuthService {
constructor(primaryWif, secondaryWif = null) {
super(primaryWif);
this.secondaryKey = secondaryWif ? PrivateKey.fromWif(secondaryWif) : null;
this.keyRotationDate = null;
}
async rotateToSecondaryKey() {
if (!this.secondaryKey) {
throw new Error('No secondary key configured');
}
// Switch to secondary key
this.privateKey = this.secondaryKey;
this.cachedToken = null; // Force token refresh
this.keyRotationDate = new Date();
console.log('Rotated to secondary key at', this.keyRotationDate);
}
}
Token Security
- Short expiration: Tokens expire in 30 minutes by default
- Endpoint binding: Tokens are bound to specific endpoints
- Signature verification: Always verify token signatures
Request Security
// Add request signing for extra security
function signRequest(method, url, body, privateKey) {
const timestamp = Date.now();
const bodyHash = crypto.createHash('sha256').update(body || '').digest('hex');
const message = `${method}:${url}:${bodyHash}:${timestamp}`;
return {
signature: SignedMessage.sign(message, privateKey),
timestamp,
bodyHash
};
}
// Use in requests
const requestSig = signRequest('POST', '/api/data', JSON.stringify(data), privateKey);
const response = await fetch('/api/data', {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'X-Signature': requestSig.signature,
'X-Timestamp': requestSig.timestamp,
'X-Body-Hash': requestSig.bodyHash,
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
});
Error Handling
Handle common authentication token errors:
class AuthTokenError extends Error {
constructor(message, code, statusCode = 500) {
super(message);
this.code = code;
this.statusCode = statusCode;
}
}
async function handleTokenErrors(operation) {
try {
return await operation();
} catch (error) {
if (error.message.includes('signature verification failed')) {
throw new AuthTokenError('Invalid signature', 'INVALID_SIGNATURE', 401);
}
if (error.message.includes('timestamp too old')) {
throw new AuthTokenError('Request expired', 'TIMESTAMP_EXPIRED', 401);
}
if (error.message.includes('rate limit')) {
throw new AuthTokenError('Too many requests', 'RATE_LIMITED', 429);
}
if (error.message.includes('token expired')) {
throw new AuthTokenError('Token expired', 'TOKEN_EXPIRED', 401);
}
throw new AuthTokenError('Authentication failed', 'AUTH_FAILED', 401);
}
}
// Usage
try {
const token = await handleTokenErrors(() => authService.getToken(endpoint));
// Use token...
} catch (error) {
if (error instanceof AuthTokenError) {
console.error(`Auth error [${error.code}]:`, error.message);
if (error.code === 'TOKEN_EXPIRED') {
// Retry with force refresh
const newToken = await authService.getToken(endpoint, true);
}
} else {
console.error('Unexpected error:', error);
}
}
Testing Backend Authentication
Test your backend authentication flow:
import { describe, it, expect, beforeAll } from '@jest/globals';
describe('Backend Authentication', () => {
let authService;
beforeAll(() => {
authService = new BackendAuthService(process.env.TEST_SERVICE_WIF);
});
it('should generate valid signatures', async () => {
const endpoint = 'https://test.example.com';
const signature = await authService.generateSignature(endpoint);
expect(signature.message).toContain(endpoint);
expect(signature.signature).toBeDefined();
expect(signature.pubkey).toBeDefined();
expect(signature.timestamp).toBeInstanceOf(Number);
});
it('should obtain access tokens', async () => {
const endpoint = 'https://test.example.com';
const token = await authService.getToken(endpoint);
expect(token).toMatch(/^[A-Za-z0-9-_]+\.[A-Za-z0-9-_]+\.[A-Za-z0-9-_]+$/); // JWT format
});
it('should cache tokens appropriately', async () => {
const endpoint = 'https://test.example.com';
const token1 = await authService.getToken(endpoint);
const token2 = await authService.getToken(endpoint);
expect(token1).toBe(token2); // Should return cached token
});
it('should make authenticated requests', async () => {
const response = await authService.authenticatedGet('https://auth.sigmaidentity.com/userinfo');
expect(response.ok).toBe(true);
const data = await response.json();
expect(data.pubkey).toBeDefined();
});
});
Backend auth tokens provide a secure, programmatic way to authenticate your services with Sigma Auth without requiring user interaction. This pattern is perfect for server-to-server communication, automated processes, and API integrations while maintaining the security benefits of Bitcoin cryptographic signatures.