Building Aegis2FA: Enterprise 2FA Without Vendor Lock-in
How I built a production-ready two-factor authentication service that saves businesses $1,188/year while maintaining enterprise-grade security.
The Problem
Companies need secure 2FA solutions, but existing services like Auth0 and Okta cost $99+/month and lock you into their platforms. For small businesses and startups, this creates a dilemma: sacrifice security or pay premium prices.
The Solution
I built Aegis2FA - a self-hosted, production-ready 2FA service with:
- Multiple authentication methods (TOTP, SMS, Email, Backup codes)
- Enterprise-grade security (Argon2id, JWT with refresh tokens)
- Comprehensive documentation
- 80%+ test coverage
- Zero-budget deployment options
Technical Architecture
Backend Stack
// Core authentication flow with NestJS
@Injectable()
export class TwoFactorService {
async verifyTotp(userId: string, token: string): Promise<boolean> {
const user = await this.userRepository.findById(userId);
// Verify token using speakeasy
return speakeasy.totp.verify({
secret: user.totpSecret,
encoding: 'base32',
token,
window: 2, // Allow 60-second time skew
});
}
}Key Design Decisions
Why Argon2id over bcrypt?
Winner of the Password Hashing Competition, resistant to GPU cracking attacks, with configurable memory and time costs, and better protection against side-channel attacks.
Why JWT with refresh tokens?
Stateless authentication with short-lived access tokens (15min) and long-lived refresh tokens (7 days) stored securely with automatic rotation on use.
Challenges Faced
Race Conditions in Token Generation
Problem: Multiple simultaneous requests could generate duplicate tokens.
Solution: Implemented Redis-based distributed locking:
async generateBackupCodes(userId: string): Promise<string[]> {
const lock = await this.redisClient.acquireLock(`backup:${userId}`, 5000);
try {
// Generate codes with cryptographically secure random
const codes = Array.from({ length: 10 }, () =>
crypto.randomBytes(4).toString('hex')
);
// Hash and store
await this.storeHashedCodes(userId, codes);
return codes;
} finally {
await lock.release();
}
}SMS Delivery Reliability
Problem: SMS providers have varying delivery times and failure rates.
Solution: Implemented a fallback queue system with exponential backoff and multiple provider support.
TOTP Clock Drift
Problem: User devices may have inaccurate clocks, causing valid tokens to be rejected.
Solution: Added a configurable time window of ±60 seconds and implemented NTP sync recommendations in the client app.
Impact & Results
Results after 6 months of production use:
$1,188/year saved compared to Auth0's Team plan
99.9% uptime over 6 months of self-hosting
Sub-100ms average authentication response time
Zero CVEs with no known security vulnerabilities
80%+ test coverage with integration and e2e tests
Lessons Learned
Security is not optional: Every endpoint needs rate limiting, every password needs proper hashing, every token needs validation.
Documentation is a feature: Spent 30% of development time on docs. Result? Zero support tickets about how to integrate.
Test coverage matters: Integration tests caught 3 critical race conditions before production.
Self-hosting is viable: With Docker and proper monitoring, you don't need expensive managed services.
What's Next
Planning to add WebAuthn/FIDO2 support for passwordless authentication, an admin dashboard for user management, Terraform modules for one-click AWS deployment, and multi-region support with automatic failover.
Try It Yourself
Check out the project on GitHub at github.com/Ilia01/Aegis2FA, read the full documentation at ilia01.github.io/Aegis2FA, or try the interactive demo without signing up.
Have questions about the implementation? Found a bug? Open an issue on GitHub or reach out via email.