Deploying Firefox Account Server
Introduction
Firefox Account Server (FxA) is Mozilla’s robust, production-ready authentication and identity service that powers Firefox Sync, Mozilla VPN, and other Mozilla services. As a self-hosted solution, Firefox Account Server provides enterprise-grade authentication, OAuth 2.0 integration, two-factor authentication, and a complete user identity management system with the privacy and control of running on your own infrastructure.
Firefox Account Server delivers powerful identity management capabilities:
- OAuth 2.0 Provider: Complete OAuth 2.0 and OpenID Connect implementation for single sign-on
- Firefox Sync Support: Enable browser synchronization for bookmarks, passwords, history, and tabs
- Two-Factor Authentication: Built-in TOTP support for enhanced security
- Email Verification: Automatic email verification workflows with customizable templates
- Session Management: Sophisticated session tracking and device management
- API-First Architecture: RESTful API for integration with custom applications
- Account Recovery: Secure account recovery flows with email-based verification
- Profile Management: User profile data storage with avatar support
- Security Events: Comprehensive audit logging and security event tracking
- Rate Limiting: Built-in protection against brute force and abuse
- Multi-Language Support: Internationalization support for global deployments
- Webhook Integration: Real-time notifications for account events
Whether you need centralized authentication for internal tools, want to provide Firefox Sync for your organization, or require a privacy-focused OAuth provider, Firefox Account Server delivers a battle-tested solution with Mozilla’s security standards.
This comprehensive guide walks you through deploying Firefox Account Server on Klutch.sh using Docker, including MySQL and Redis configuration, OAuth setup, environment variables, security best practices, and production deployment strategies.
Prerequisites
Before you begin, ensure you have the following:
- A Klutch.sh account
- A GitHub account with a repository for your project
- Docker installed locally for testing (optional but recommended)
- Basic understanding of OAuth 2.0 and authentication systems
- Access to MySQL and Redis instances (deploy separately on Klutch.sh - see MySQL and Redis guides)
- SMTP server credentials for sending verification emails
Architecture Overview
Firefox Account Server requires several components working together:
- FxA Server: The main authentication service (Node.js application)
- MySQL Database: Stores user accounts, sessions, and authentication tokens
- Redis Cache: Handles session caching and rate limiting
- SMTP Server: Sends verification and notification emails
Deployment Strategy: Deploy each component as a separate app on Klutch.sh:
- MySQL database with persistent volume (see MySQL guide)
- Redis cache with persistent volume (see Redis guide)
- Firefox Account Server (this guide)
Installation and Setup
Step 1: Create Your Project Directory
First, create a new directory for your Firefox Account Server deployment:
mkdir firefox-accounts-klutchcd firefox-accounts-klutchgit initStep 2: Create the Dockerfile
Create a Dockerfile in your project root. Firefox Account Server requires Node.js and specific configuration:
FROM node:18-alpine
# Install system dependenciesRUN apk add --no-cache \ git \ python3 \ make \ g++ \ cairo-dev \ jpeg-dev \ pango-dev \ giflib-dev
# Set working directoryWORKDIR /app
# Clone Firefox Account Server repositoryRUN git clone --depth 1 --branch v1.232.0 https://github.com/mozilla/fxa.git .
# Install dependencies for the auth serverWORKDIR /app/packages/fxa-auth-serverRUN npm ci --production
# Create configuration directoryRUN mkdir -p /app/config
# Expose the default FxA portEXPOSE 9000
# Set environment variablesENV NODE_ENV=production \ PORT=9000 \ HOST=0.0.0.0
# Health checkHEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \ CMD node -e "require('http').get('http://localhost:9000/__heartbeat__', (res) => { process.exit(res.statusCode === 200 ? 0 : 1); })"
# Start the auth serverCMD ["node", "bin/key_server.js"]Step 3: Advanced Production Dockerfile
For production deployments with custom configuration and optimization:
FROM node:18-alpine AS builder
# Install build dependenciesRUN apk add --no-cache \ git \ python3 \ make \ g++ \ cairo-dev \ jpeg-dev \ pango-dev \ giflib-dev
WORKDIR /app
# Clone specific version of FxAARG FXA_VERSION=v1.232.0RUN git clone --depth 1 --branch ${FXA_VERSION} https://github.com/mozilla/fxa.git .
# Build auth serverWORKDIR /app/packages/fxa-auth-serverRUN npm ci --production && \ npm cache clean --force
# Production stageFROM node:18-alpine
# Install runtime dependencies onlyRUN apk add --no-cache \ cairo \ jpeg \ pango \ giflib \ tini
WORKDIR /app
# Copy built applicationCOPY --from=builder /app/packages/fxa-auth-server /app
# Create non-root userRUN addgroup -g 1001 fxa && \ adduser -D -u 1001 -G fxa fxa && \ chown -R fxa:fxa /app
USER fxa
# Expose portEXPOSE 9000
# Environment defaultsENV NODE_ENV=production \ PORT=9000 \ HOST=0.0.0.0 \ LOG_LEVEL=info
# Health checkHEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \ CMD node -e "require('http').get('http://localhost:9000/__heartbeat__', (res) => { process.exit(res.statusCode === 200 ? 0 : 1); })"
# Use tini for proper signal handlingENTRYPOINT ["/sbin/tini", "--"]
# Start serverCMD ["node", "bin/key_server.js"]Step 4: Create Configuration File Template
Create a config/production.json file for custom configuration:
{ "publicUrl": "https://example-app.klutch.sh", "contentUrl": "https://example-app.klutch.sh", "listen": { "host": "0.0.0.0", "port": 9000 }, "mysql": { "host": "your-mysql-app.klutch.sh", "port": 8000, "database": "fxa", "user": "fxa_user", "password": "your-secure-password", "connectionLimit": 10, "timezone": "UTC" }, "redis": { "host": "your-redis-app.klutch.sh", "port": 8000, "password": "your-redis-password", "db": 0, "keyPrefix": "fxa:" }, "smtp": { "host": "smtp.example.com", "port": 587, "secure": false, "auth": { "user": "noreply@example.com", "pass": "your-smtp-password" } }, "emailService": { "from": "Firefox Accounts <noreply@example.com>", "verificationUrl": "https://example-app.klutch.sh/verify", "supportUrl": "https://support.example.com" }, "oauth": { "secretKey": "generate-a-random-32-byte-hex-string", "clientIds": { "your-app": "your-app-client-id" } }, "securityHistory": { "enabled": true, "ipProfiling": { "enabled": true, "threshold": 0.95 } }, "signinConfirmation": { "enabled": true, "forcedEmailAddresses": ["suspicious-pattern@example.com"] }}Step 5: Create Environment Variables Template
Create a .env.example file to document required environment variables:
# Application URLsPUBLIC_URL=https://example-app.klutch.shCONTENT_URL=https://example-app.klutch.sh
# Server ConfigurationNODE_ENV=productionPORT=9000HOST=0.0.0.0LOG_LEVEL=info
# MySQL Database (Deploy separately on Klutch.sh)MYSQL_HOST=your-mysql-app.klutch.shMYSQL_PORT=8000MYSQL_DATABASE=fxaMYSQL_USER=fxa_userMYSQL_PASSWORD=your-secure-password
# Redis Cache (Deploy separately on Klutch.sh)REDIS_HOST=your-redis-app.klutch.shREDIS_PORT=8000REDIS_PASSWORD=your-redis-password
# SMTP Email ConfigurationSMTP_HOST=smtp.example.comSMTP_PORT=587SMTP_SECURE=falseSMTP_USER=noreply@example.comSMTP_PASSWORD=your-smtp-passwordEMAIL_FROM=Firefox Accounts <noreply@example.com>
# OAuth and SecurityOAUTH_SECRET_KEY=generate-a-random-32-byte-hex-string-hereSESSION_SECRET=another-random-32-byte-hex-stringVERIFICATION_SECRET=third-random-32-byte-hex-string
# Feature FlagsSIGNIN_CONFIRMATION_ENABLED=trueSECURITY_HISTORY_ENABLED=trueTOTP_ENABLED=true
# Rate LimitingRATE_LIMIT_ENABLED=trueMAX_VERIFY_CODES=3MAX_FAILED_LOGIN_ATTEMPTS=5
# Account SettingsACCOUNT_DELETION_ENABLED=truePROFILE_IMAGES_ENABLED=trueEMAIL_CHANGE_ENABLED=trueSecurity Note: Never commit actual credentials to your repository. Use these as templates and configure actual values in the Klutch.sh dashboard.
Step 6: Create Database Schema
Create a schema.sql file to initialize your MySQL database:
-- Firefox Account Server Database SchemaCREATE DATABASE IF NOT EXISTS fxa CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;USE fxa;
-- Accounts tableCREATE TABLE IF NOT EXISTS accounts ( uid BINARY(16) PRIMARY KEY, normalizedEmail VARCHAR(255) NOT NULL UNIQUE, email VARCHAR(255) NOT NULL, emailCode BINARY(16) NOT NULL, emailVerified TINYINT(1) NOT NULL DEFAULT 0, kA BINARY(32) NOT NULL, wrapWrapKb BINARY(32) NOT NULL, authSalt BINARY(32) NOT NULL, verifierVersion TINYINT UNSIGNED NOT NULL, verifyHash BINARY(32) NOT NULL, verifierSetAt BIGINT UNSIGNED NOT NULL, createdAt BIGINT UNSIGNED NOT NULL, locale VARCHAR(255), lockedAt BIGINT UNSIGNED DEFAULT NULL, INDEX idx_email (normalizedEmail), INDEX idx_created (createdAt)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- Session tokensCREATE TABLE IF NOT EXISTS sessionTokens ( tokenId BINARY(32) PRIMARY KEY, tokenData BINARY(32) NOT NULL, uid BINARY(16) NOT NULL, createdAt BIGINT UNSIGNED NOT NULL, uaBrowser VARCHAR(255), uaBrowserVersion VARCHAR(255), uaOS VARCHAR(255), uaOSVersion VARCHAR(255), uaDeviceType VARCHAR(255), lastAccessTime BIGINT UNSIGNED NOT NULL, INDEX idx_uid (uid), FOREIGN KEY (uid) REFERENCES accounts(uid) ON DELETE CASCADE) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- OAuth tokensCREATE TABLE IF NOT EXISTS oauthTokens ( tokenId BINARY(32) PRIMARY KEY, clientId BINARY(8) NOT NULL, userId BINARY(16) NOT NULL, email VARCHAR(255) NOT NULL, scope VARCHAR(255) NOT NULL, createdAt BIGINT UNSIGNED NOT NULL, expiresAt BIGINT UNSIGNED NOT NULL, INDEX idx_user (userId), FOREIGN KEY (userId) REFERENCES accounts(uid) ON DELETE CASCADE) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- Security eventsCREATE TABLE IF NOT EXISTS securityEvents ( id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, uid BINARY(16) NOT NULL, tokenId BINARY(32), nameId VARCHAR(255) NOT NULL, verified TINYINT(1) NOT NULL DEFAULT 0, createdAt BIGINT UNSIGNED NOT NULL, INDEX idx_uid_created (uid, createdAt), FOREIGN KEY (uid) REFERENCES accounts(uid) ON DELETE CASCADE) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- Email bouncesCREATE TABLE IF NOT EXISTS emailBounces ( id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, email VARCHAR(255) NOT NULL UNIQUE, bounceType VARCHAR(50) NOT NULL, bounceSubType VARCHAR(50), createdAt BIGINT UNSIGNED NOT NULL, INDEX idx_email (email)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- Account reset tokensCREATE TABLE IF NOT EXISTS accountResetTokens ( tokenId BINARY(32) PRIMARY KEY, tokenData BINARY(32) NOT NULL, uid BINARY(16) NOT NULL, createdAt BIGINT UNSIGNED NOT NULL, INDEX idx_uid (uid), FOREIGN KEY (uid) REFERENCES accounts(uid) ON DELETE CASCADE) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- Password forgot tokensCREATE TABLE IF NOT EXISTS passwordForgotTokens ( tokenId BINARY(32) PRIMARY KEY, tokenData BINARY(32) NOT NULL, uid BINARY(16) NOT NULL, passCode BINARY(16) NOT NULL, createdAt BIGINT UNSIGNED NOT NULL, tries TINYINT NOT NULL DEFAULT 0, INDEX idx_uid (uid), FOREIGN KEY (uid) REFERENCES accounts(uid) ON DELETE CASCADE) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- Password change tokensCREATE TABLE IF NOT EXISTS passwordChangeTokens ( tokenId BINARY(32) PRIMARY KEY, tokenData BINARY(32) NOT NULL, uid BINARY(16) NOT NULL, createdAt BIGINT UNSIGNED NOT NULL, INDEX idx_uid (uid), FOREIGN KEY (uid) REFERENCES accounts(uid) ON DELETE CASCADE) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- Device metadataCREATE TABLE IF NOT EXISTS devices ( id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, uid BINARY(16) NOT NULL, sessionTokenId BINARY(32) NOT NULL, name VARCHAR(255), type VARCHAR(50), createdAt BIGINT UNSIGNED NOT NULL, callbackURL VARCHAR(255), callbackPublicKey VARCHAR(255), callbackAuthKey VARCHAR(255), callbackIsExpired TINYINT(1) NOT NULL DEFAULT 0, INDEX idx_uid (uid), INDEX idx_session (sessionTokenId), FOREIGN KEY (uid) REFERENCES accounts(uid) ON DELETE CASCADE, FOREIGN KEY (sessionTokenId) REFERENCES sessionTokens(tokenId) ON DELETE CASCADE) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- TOTP tokensCREATE TABLE IF NOT EXISTS totpTokens ( uid BINARY(16) PRIMARY KEY, sharedSecret VARCHAR(255) NOT NULL, epoch BIGINT UNSIGNED NOT NULL, createdAt BIGINT UNSIGNED NOT NULL, verified TINYINT(1) NOT NULL DEFAULT 0, enabled TINYINT(1) NOT NULL DEFAULT 0, FOREIGN KEY (uid) REFERENCES accounts(uid) ON DELETE CASCADE) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- Recovery codesCREATE TABLE IF NOT EXISTS recoveryCodes ( uid BINARY(16) NOT NULL, codeHash BINARY(32) NOT NULL, INDEX idx_uid (uid), PRIMARY KEY (uid, codeHash), FOREIGN KEY (uid) REFERENCES accounts(uid) ON DELETE CASCADE) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;Step 7: Create README Documentation
Create a README.md file with deployment instructions:
# Firefox Account Server Deployment on Klutch.sh
## About
Self-hosted Firefox Account Server providing OAuth 2.0 authentication, Firefox Sync, and identity management.
## Prerequisites
Before deploying FxA, you need:
1. **MySQL Database** - Deploy separately on Klutch.sh - See the [MySQL deployment guide](/guides/databases/mysql) - Create database: `fxa` - Run the schema.sql file to initialize tables
2. **Redis Cache** - Deploy separately on Klutch.sh - See the [Redis deployment guide](/guides/databases/redis) - Enable persistence for session data
3. **SMTP Server** - For sending verification emails - Any SMTP provider (SendGrid, Mailgun, AWS SES, etc.) - Configure credentials in environment variables
## Initial Setup
After deployment:
1. Initialize the MySQL database with `schema.sql`2. Configure environment variables in Klutch.sh dashboard3. Access your FxA instance at your app URL4. Create admin account via API5. Configure OAuth clients for your applications
## Security Recommendations
- Use strong, unique secrets for OAuth and sessions- Enable HTTPS (automatic on Klutch.sh)- Configure rate limiting to prevent abuse- Enable two-factor authentication for admin accounts- Regular database backups (see MySQL guide)- Monitor security event logs- Implement IP whitelisting for admin endpoints
## API Endpoints
- `POST /v1/account/create` - Create new account- `POST /v1/account/login` - Login to account- `POST /v1/certificate/sign` - Sign certificate for Firefox Sync- `GET /v1/account/profile` - Get account profile- `POST /v1/password/forgot/send_code` - Send password reset code- `GET /.well-known/openid-configuration` - OAuth configuration
## Testing
Test your deployment:
```bash# Health checkcurl https://example-app.klutch.sh/__heartbeat__
# Create test accountcurl -X POST https://example-app.klutch.sh/v1/account/create \ -H "Content-Type: application/json" \ -d '{ "email": "test@example.com", "authPW": "your-hashed-password" }'### Step 8: Test Locally (Optional)
Before deploying to Klutch.sh, test your setup locally:
```bash# Build the Docker imagedocker build -t firefox-accounts .
# Create a network for containersdocker network create fxa-network
# Start MySQL (for testing - use Klutch.sh deployment in production)docker run -d \ --name mysql-test \ --network fxa-network \ -e MYSQL_ROOT_PASSWORD=rootpassword \ -e MYSQL_DATABASE=fxa \ -e MYSQL_USER=fxa_user \ -e MYSQL_PASSWORD=fxapassword \ mysql:8.0
# Start Redis (for testing - use Klutch.sh deployment in production)docker run -d \ --name redis-test \ --network fxa-network \ redis:7-alpine redis-server --requirepass redispassword
# Wait for databases to initializesleep 10
# Run FxA serverdocker run -d \ --name fxa-test \ --network fxa-network \ -p 9000:9000 \ -e MYSQL_HOST=mysql-test \ -e MYSQL_PORT=3306 \ -e MYSQL_USER=fxa_user \ -e MYSQL_PASSWORD=fxapassword \ -e REDIS_HOST=redis-test \ -e REDIS_PORT=6379 \ -e REDIS_PASSWORD=redispassword \ -e PUBLIC_URL=http://localhost:9000 \ firefox-accounts
# View logsdocker logs -f fxa-test
# Test the servercurl http://localhost:9000/__heartbeat__
# Cleanup when donedocker stop fxa-test redis-test mysql-testdocker rm fxa-test redis-test mysql-testdocker network rm fxa-networkStep 9: Push to GitHub
Commit your files and push to your GitHub repository:
git add Dockerfile config/ schema.sql .env.example README.mdgit commit -m "Add Firefox Account Server deployment configuration"git remote add origin https://github.com/yourusername/firefox-accounts-klutch.gitgit branch -M maingit push -u origin mainDeploying to Klutch.sh
Now that your Firefox Account Server project is ready and pushed to GitHub, follow these steps to deploy it on Klutch.sh.
Deployment Steps
-
Deploy Dependencies First
Before deploying FxA, set up the required services:
MySQL Database:
- Follow the MySQL deployment guide
- Create a database named
fxa - Execute the
schema.sqlfile to initialize tables - Note the connection URL:
your-mysql-app.klutch.sh:8000
Redis Cache:
- Follow the Redis deployment guide
- Enable password protection
- Enable persistence (AOF or RDB)
- Note the connection URL:
your-redis-app.klutch.sh:8000
-
Log in to Klutch.sh
Navigate to klutch.sh/app and sign in to your account.
-
Create a New Project
Click on “Create Project” and give your project a meaningful name (e.g., “Firefox Account Server”).
-
Create a New App
Click on “Create App” within your project and configure the following settings.
-
Select Your Repository
- Choose GitHub as your Git source
- Select the repository containing your Dockerfile
- Choose the branch you want to deploy (usually
mainormaster)
-
Configure Traffic Type
- Traffic Type: Select HTTP (FxA serves a web API via HTTP)
- Internal Port: Set to
9000(the default port Firefox Account Server listens on)
-
Configure Environment Variables
Add the following environment variables in the Klutch.sh dashboard (replace with your actual values):
PUBLIC_URL=https://your-fxa-app.klutch.shCONTENT_URL=https://your-fxa-app.klutch.shNODE_ENV=productionPORT=9000HOST=0.0.0.0LOG_LEVEL=infoMYSQL_HOST=your-mysql-app.klutch.shMYSQL_PORT=8000MYSQL_DATABASE=fxaMYSQL_USER=fxa_userMYSQL_PASSWORD=your-secure-mysql-passwordREDIS_HOST=your-redis-app.klutch.shREDIS_PORT=8000REDIS_PASSWORD=your-secure-redis-passwordSMTP_HOST=smtp.example.comSMTP_PORT=587SMTP_USER=noreply@example.comSMTP_PASSWORD=your-smtp-passwordEMAIL_FROM=Firefox Accounts <noreply@example.com>OAUTH_SECRET_KEY=generate-32-byte-hex-stringSESSION_SECRET=generate-32-byte-hex-stringVERIFICATION_SECRET=generate-32-byte-hex-stringSIGNIN_CONFIRMATION_ENABLED=trueSECURITY_HISTORY_ENABLED=trueTOTP_ENABLED=trueGenerate secure secrets using:
Terminal window node -e "console.log(require('crypto').randomBytes(32).toString('hex'))" -
Attach Persistent Volume (Optional)
If you want to store logs or temporary files:
- Navigate to the “Volumes” section in your app settings
- Click “Add Volume”
- Mount Path: Enter
/app/logs - Size: Allocate 5GB for logs
- Click “Add” to attach the volume
-
Deploy Your Application
Click “Deploy” to start the deployment process. Klutch.sh will automatically:
- Detect your Dockerfile in the repository root
- Build the Docker image with Node.js and FxA
- Deploy the container with the configured settings
- Map the internal port 9000 to a public URL
-
Monitor Deployment
Watch the build logs in the Klutch.sh dashboard to ensure your deployment completes successfully. The initial build may take 5-10 minutes as it downloads dependencies and builds the FxA server.
Post-Deployment Configuration
After your Firefox Account Server is deployed and running, complete these essential setup steps:
Step 1: Verify Server Health
Check that all components are running correctly:
# Health check endpointcurl https://your-fxa-app.klutch.sh/__heartbeat__
# Expected response: {"ok":true}
# Version endpointcurl https://your-fxa-app.klutch.sh/__version__
# OpenID Connect configurationcurl https://your-fxa-app.klutch.sh/.well-known/openid-configurationStep 2: Initialize Database
Connect to your MySQL database and verify the schema:
# Connect to MySQL (use your actual connection details)mysql -h your-mysql-app.klutch.sh -P 8000 -u fxa_user -p
# Verify tables were createdUSE fxa;SHOW TABLES;
# Check accounts table structureDESCRIBE accounts;Step 3: Create First User Account
Create a test account via API:
# Generate auth password (simplified example)AUTH_PW=$(echo -n "your-password" | openssl dgst -sha256 -binary | xxd -p)
# Create accountcurl -X POST https://your-fxa-app.klutch.sh/v1/account/create \ -H "Content-Type: application/json" \ -d "{ \"email\": \"admin@example.com\", \"authPW\": \"${AUTH_PW}\" }"Step 4: Configure OAuth Clients
Register your applications as OAuth clients. Create a clients.json file:
{ "clients": [ { "id": "your-app-client-id", "name": "Your Application", "imageUri": "https://your-app.com/logo.png", "redirectUri": "https://your-app.com/oauth/callback", "trusted": true, "canGrant": true, "publicClient": false, "termsUri": "https://your-app.com/terms", "privacyUri": "https://your-app.com/privacy" } ]}Register clients via the admin API or database insert.
Step 5: Test Authentication Flow
Test the complete OAuth 2.0 flow:
# 1. Initiate OAuth authorizationcurl -X GET "https://your-fxa-app.klutch.sh/v1/authorization?client_id=your-client-id&response_type=code&state=random-state&scope=profile+openid"
# 2. Login and get authorization code# (Browser interaction required)
# 3. Exchange code for access tokencurl -X POST https://your-fxa-app.klutch.sh/v1/token \ -H "Content-Type: application/json" \ -d '{ "grant_type": "authorization_code", "code": "authorization-code", "client_id": "your-client-id", "client_secret": "your-client-secret" }'
# 4. Use access token to get profilecurl https://your-fxa-app.klutch.sh/v1/account/profile \ -H "Authorization: Bearer your-access-token"Integration Examples
Node.js Application Integration
Integrate FxA authentication in a Node.js application:
const axios = require('axios');
class FxAClient { constructor(config) { this.baseURL = config.baseURL || 'https://your-fxa-app.klutch.sh'; this.clientId = config.clientId; this.clientSecret = config.clientSecret; this.redirectUri = config.redirectUri; }
// Generate authorization URL getAuthorizationUrl(state, scope = 'profile openid') { const params = new URLSearchParams({ client_id: this.clientId, response_type: 'code', state: state, scope: scope, redirect_uri: this.redirectUri }); return `${this.baseURL}/v1/authorization?${params.toString()}`; }
// Exchange authorization code for access token async getAccessToken(code) { try { const response = await axios.post(`${this.baseURL}/v1/token`, { grant_type: 'authorization_code', code: code, client_id: this.clientId, client_secret: this.clientSecret, redirect_uri: this.redirectUri }); return response.data; } catch (error) { throw new Error(`Token exchange failed: ${error.message}`); } }
// Get user profile async getUserProfile(accessToken) { try { const response = await axios.get(`${this.baseURL}/v1/account/profile`, { headers: { 'Authorization': `Bearer ${accessToken}` } }); return response.data; } catch (error) { throw new Error(`Profile fetch failed: ${error.message}`); } }
// Verify email async verifyEmail(uid, code) { try { const response = await axios.post(`${this.baseURL}/v1/recovery_email/verify_code`, { uid: uid, code: code }); return response.data; } catch (error) { throw new Error(`Email verification failed: ${error.message}`); } }
// Logout and destroy session async logout(accessToken) { try { await axios.post(`${this.baseURL}/v1/account/destroy`, {}, { headers: { 'Authorization': `Bearer ${accessToken}` } }); return true; } catch (error) { throw new Error(`Logout failed: ${error.message}`); } }}
// Usage exampleconst fxaClient = new FxAClient({ baseURL: 'https://your-fxa-app.klutch.sh', clientId: 'your-client-id', clientSecret: 'your-client-secret', redirectUri: 'https://your-app.com/oauth/callback'});
// Express.js route exampleapp.get('/login', (req, res) => { const state = crypto.randomBytes(16).toString('hex'); req.session.oauthState = state; const authUrl = fxaClient.getAuthorizationUrl(state); res.redirect(authUrl);});
app.get('/oauth/callback', async (req, res) => { const { code, state } = req.query;
if (state !== req.session.oauthState) { return res.status(400).send('Invalid state'); }
try { const tokenData = await fxaClient.getAccessToken(code); const profile = await fxaClient.getUserProfile(tokenData.access_token);
req.session.user = { email: profile.email, uid: profile.uid, accessToken: tokenData.access_token };
res.redirect('/dashboard'); } catch (error) { res.status(500).send('Authentication failed'); }});
module.exports = fxaClient;Python Application Integration
Integrate FxA with a Python/Flask application:
import requestsimport secretsfrom urllib.parse import urlencode
class FxAClient: def __init__(self, base_url, client_id, client_secret, redirect_uri): self.base_url = base_url self.client_id = client_id self.client_secret = client_secret self.redirect_uri = redirect_uri
def get_authorization_url(self, state, scope='profile openid'): """Generate OAuth authorization URL""" params = { 'client_id': self.client_id, 'response_type': 'code', 'state': state, 'scope': scope, 'redirect_uri': self.redirect_uri } return f"{self.base_url}/v1/authorization?{urlencode(params)}"
def get_access_token(self, code): """Exchange authorization code for access token""" url = f"{self.base_url}/v1/token" data = { 'grant_type': 'authorization_code', 'code': code, 'client_id': self.client_id, 'client_secret': self.client_secret, 'redirect_uri': self.redirect_uri }
response = requests.post(url, json=data) response.raise_for_status() return response.json()
def get_user_profile(self, access_token): """Fetch user profile with access token""" url = f"{self.base_url}/v1/account/profile" headers = {'Authorization': f'Bearer {access_token}'}
response = requests.get(url, headers=headers) response.raise_for_status() return response.json()
def verify_email(self, uid, code): """Verify user email address""" url = f"{self.base_url}/v1/recovery_email/verify_code" data = {'uid': uid, 'code': code}
response = requests.post(url, json=data) response.raise_for_status() return response.json()
def create_account(self, email, auth_pw): """Create new user account""" url = f"{self.base_url}/v1/account/create" data = {'email': email, 'authPW': auth_pw}
response = requests.post(url, json=data) response.raise_for_status() return response.json()
# Flask application examplefrom flask import Flask, redirect, request, session, url_forimport os
app = Flask(__name__)app.secret_key = os.urandom(32)
fxa_client = FxAClient( base_url='https://your-fxa-app.klutch.sh', client_id=os.environ['FXA_CLIENT_ID'], client_secret=os.environ['FXA_CLIENT_SECRET'], redirect_uri='https://your-app.com/oauth/callback')
@app.route('/login')def login(): state = secrets.token_urlsafe(16) session['oauth_state'] = state auth_url = fxa_client.get_authorization_url(state) return redirect(auth_url)
@app.route('/oauth/callback')def oauth_callback(): code = request.args.get('code') state = request.args.get('state')
if state != session.get('oauth_state'): return 'Invalid state', 400
try: token_data = fxa_client.get_access_token(code) profile = fxa_client.get_user_profile(token_data['access_token'])
session['user'] = { 'email': profile['email'], 'uid': profile['uid'], 'access_token': token_data['access_token'] }
return redirect(url_for('dashboard')) except Exception as e: return f'Authentication failed: {str(e)}', 500
@app.route('/dashboard')def dashboard(): if 'user' not in session: return redirect(url_for('login')) return f"Welcome {session['user']['email']}"
if __name__ == '__main__': app.run(debug=True)Production Best Practices
Security Hardening
Implement these security measures for production deployments:
-
Strong Secret Keys
Terminal window # Generate cryptographically secure secretsnode -e "console.log(require('crypto').randomBytes(32).toString('hex'))" -
Rate Limiting Configuration
- Enable rate limiting in environment variables
- Configure limits based on your traffic patterns
- Monitor for abuse patterns
-
Email Security
- Use dedicated SMTP service (SendGrid, Mailgun, AWS SES)
- Implement SPF, DKIM, and DMARC records
- Monitor bounce rates and delivery issues
-
Database Security
- Use strong passwords for MySQL and Redis
- Enable SSL/TLS for database connections
- Regular backups (see MySQL guide)
- Implement query timeout limits
-
Session Management
- Set appropriate session timeout values
- Enable secure session cookies
- Implement device tracking
- Force re-authentication for sensitive operations
Performance Optimization
Optimize your FxA deployment for better performance:
-
Redis Caching Strategy
Terminal window # Configure Redis for optimal cachingREDIS_MAX_MEMORY=2gbREDIS_EVICTION_POLICY=allkeys-lruREDIS_PERSISTENCE=yes -
Database Connection Pooling
- Configure appropriate connection pool size
- Monitor connection usage and timeouts
- Implement read replicas for high traffic
-
Resource Allocation
- Allocate sufficient memory (2GB minimum recommended)
- Monitor CPU usage during peak times
- Scale horizontally if needed
-
CDN for Static Assets
- Serve verification pages through CDN
- Cache public assets (logos, CSS, JavaScript)
- Reduce latency for global users
Monitoring and Logging
Set up comprehensive monitoring:
-
Health Checks
Terminal window # Monitor these endpointshttps://your-fxa-app.klutch.sh/__heartbeat__https://your-fxa-app.klutch.sh/__lbheartbeat__https://your-fxa-app.klutch.sh/__version__ -
Key Metrics to Track
- Account creation rate
- Login success/failure rates
- Token generation frequency
- Email delivery rates
- API response times
- Database query performance
- Redis cache hit/miss ratio
-
Error Logging
- Configure structured logging
- Set appropriate log levels (info, warn, error)
- Implement log aggregation
- Set up alerts for critical errors
-
Security Event Monitoring
- Track failed login attempts
- Monitor suspicious IP addresses
- Alert on unusual account activity
- Log OAuth authorization flows
Backup and Disaster Recovery
Implement robust backup strategies:
-
Database Backups
- Automated daily MySQL backups
- Point-in-time recovery capability
- Test restore procedures regularly
- Store backups off-site
-
Configuration Backups
- Version control all configuration files
- Document environment variables
- Backup OAuth client configurations
- Store encryption keys securely (use secrets manager)
-
Disaster Recovery Plan
- Document recovery procedures
- Test failover scenarios
- Maintain standby instances for critical services
- Define RTO (Recovery Time Objective) and RPO (Recovery Point Objective)
Troubleshooting
Common Issues and Solutions
Issue: Server won’t start - “Cannot connect to MySQL”
Solution:
# Verify MySQL is runningcurl https://your-mysql-app.klutch.sh:8000
# Check MySQL credentials in environment variables# Verify database 'fxa' existsmysql -h your-mysql-app.klutch.sh -P 8000 -u fxa_user -p -e "SHOW DATABASES;"
# Check FxA logs for specific connection errorsIssue: “Redis connection refused”
Solution:
# Test Redis connectivityredis-cli -h your-redis-app.klutch.sh -p 8000 -a your-redis-password PING
# Verify Redis environment variables# Check if Redis requires password authentication# Ensure Redis is accepting connections from FxA serverIssue: Email verification not working
Solution:
# Test SMTP connectioncurl -v telnet://smtp.example.com:587
# Check email logs in FxA# Verify SMTP credentials are correct# Check spam folder for test emails# Verify DNS records (SPF, DKIM, DMARC)Issue: OAuth authorization fails
Solution:
# Verify OAuth client is registered# Check redirect_uri matches exactly# Ensure client_secret is correct# Verify scope permissions are configured# Check PUBLIC_URL matches your actual URLIssue: High memory usage
Solution:
# Check for memory leaks in logs# Monitor Redis memory usage# Optimize database queries# Increase container memory allocation# Implement query result paginationIssue: Slow response times
Solution:
# Check database connection pool size# Monitor slow query log in MySQL# Verify Redis cache is working# Check network latency to dependencies# Implement query optimizationDebugging Tips
Enable detailed logging for troubleshooting:
# Set detailed logging levelLOG_LEVEL=debug
# Enable query logging in MySQL# Enable command logging in Redis# Check application logs in Klutch.sh dashboard# Use health check endpoints to isolate issuesAccess database for debugging:
# Check active sessionsSELECT COUNT(*) FROM sessionTokens WHERE lastAccessTime > UNIX_TIMESTAMP() - 3600;
# Check recent accountsSELECT email, createdAt, emailVerified FROM accounts ORDER BY createdAt DESC LIMIT 10;
# Check failed login attemptsSELECT nameId, COUNT(*) as attempts FROM securityEventsWHERE nameId = 'account.login' AND verified = 0GROUP BY nameId;
# Check OAuth tokensSELECT clientId, COUNT(*) as token_count FROM oauthTokensWHERE expiresAt > UNIX_TIMESTAMP()GROUP BY clientId;Advanced Configuration
Multi-Region Deployment
Deploy FxA across multiple regions for better performance:
- Deploy separate FxA instances in different regions
- Use a shared MySQL primary with read replicas
- Configure Redis sentinel for high availability
- Implement global load balancer
- Configure geo-routing for optimal latency
Custom Email Templates
Customize verification and notification emails:
- Create custom email templates directory
- Override default templates with branded versions
- Implement multi-language email support
- Add custom verification flows
- Configure email preview testing
WebAuthn Support
Enable passwordless authentication with WebAuthn:
- Configure WebAuthn options in environment
- Enable biometric authentication
- Implement device registration flows
- Configure authenticator attestation
- Test with hardware security keys
Custom OAuth Scopes
Define custom scopes for your applications:
- Register custom scope definitions
- Implement scope validation logic
- Configure scope-based access control
- Document scope requirements for clients
- Implement scope consent UI
Use Cases
Enterprise Single Sign-On
Use FxA as central authentication for internal applications:
- Unified login across all company tools
- Centralized user management and provisioning
- MFA enforcement for sensitive applications
- Audit logging for compliance requirements
- Role-based access control integration
Multi-Tenant SaaS Platform
Implement FxA for SaaS authentication:
- Per-tenant OAuth client configuration
- Custom branding per tenant
- Usage tracking and billing integration
- Tenant isolation and security
- API rate limiting per tenant
Firefox Sync for Organizations
Enable browser synchronization for teams:
- Organization-controlled sync server
- Enhanced privacy and data control
- Custom sync policies and rules
- Compliance with data residency requirements
- Integration with MDM solutions
Resources and Further Reading
Official Documentation
- Firefox Account Server GitHub Repository
- Mozilla Account Documentation
- FxA API Documentation
- OAuth 2.0 Integration Guide
Klutch.sh Resources
- Getting Started Guide
- MySQL Database Guide
- Redis Cache Guide
- Persistent Volumes
- Environment Variables
- Custom Domains
Related Tools and Services
Conclusion
Deploying Firefox Account Server on Klutch.sh provides a powerful, self-hosted authentication solution with enterprise-grade security and Mozilla’s proven architecture. With this guide, you have everything needed to:
- Deploy FxA with Docker on Klutch.sh
- Configure MySQL and Redis dependencies
- Implement OAuth 2.0 authentication flows
- Integrate FxA with Node.js and Python applications
- Secure your deployment with best practices
- Monitor and optimize for production use
- Troubleshoot common issues
- Scale for enterprise workloads
Your Firefox Account Server on Klutch.sh offers a privacy-focused, self-hosted alternative to commercial authentication providers, giving you complete control over user identity and authentication flows while maintaining compatibility with the broader Mozilla ecosystem.
For questions, issues, or contributions, refer to the official Firefox Account Server repository and Klutch.sh documentation. Regular updates and security patches ensure your deployment remains secure and up-to-date with the latest authentication standards.