Back to blog

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.

#authentication#security#backend#case-study

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.