Two-Factor Auth

Two-Factor Authentication (2FA)

Two-factor authentication (2FA) requires users to prove their identity with two independent factors: something they know (password) and something they have (OTP device, phone) or are (biometrics). Even if a password is stolen, the attacker cannot authenticate without the second factor.

TOTP (Time-Based One-Time Passwords)

TOTP (RFC 6238) generates a 6-digit code that changes every 30 seconds. The server and authenticator app share a secret key. Both compute HMAC-SHA1(secret, floor(time/30)) and compare the result.

Apps: Google Authenticator, Microsoft Authenticator, Authy.

// ASP.NET Core Identity: enable 2FA with TOTP
// In Program.cs:
builder.Services.AddIdentity<ApplicationUser, IdentityRole>(options =>
{
    options.SignIn.RequireConfirmedAccount = true;
})
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();

// Generate TOTP secret for a user:
var key = await _userManager.GetAuthenticatorKeyAsync(user);
if (string.IsNullOrEmpty(key))
{
    await _userManager.ResetAuthenticatorKeyAsync(user);
    key = await _userManager.GetAuthenticatorKeyAsync(user);
}
// Display key as QR code for the user to scan with their authenticator app

FIDO2 / WebAuthn

FIDO2 (Fast Identity Online) uses public-key cryptography with hardware security keys (YubiKey) or platform authenticators (Windows Hello, Touch ID). The private key never leaves the device. Phishing-resistant — the key is bound to the origin domain.

When to use: High-security applications (banking, enterprise admin access). More secure than TOTP but requires hardware or platform support.

// FIDO2 / WebAuthn with Fido2NetLib (server-side assertion verification)
// 1. During registration: store the credential public key per user
// 2. During login: verify the signed assertion

var fido2 = new Fido2(new Fido2Configuration
{
    ServerDomain = "example.com",
    ServerName = "My App",
    Origins = new HashSet<string> { "https://example.com" }
});

// Verify assertion (login step)
var result = await fido2.MakeAssertionAsync(
    clientResponse,          // JSON from navigator.credentials.get()
    options,                  // stored assertion options from session
    storedPublicKey,          // credential public key from registration
    storedSignCount,          // replay attack counter
    isUserHandleOwnerOfCredential);

// result.Status == "ok" means authentication succeeded
// result.Counter must be > storedSignCount (replay protection)

Pitfalls

Tradeoffs

Method Phishing Resistance Hardware Required Implementation Complexity Use when
SMS OTP None (SIM swap, SS7) No Minimal Legacy systems; low-security consumer apps where UX matters most
TOTP (Google Authenticator) Low (code can be phished) Authenticator app Low Most applications; good balance of security and UX
Push notification (Duo, Okta) Low (MFA fatigue attacks) Smartphone Medium Enterprise SSO; users are trained to verify context before approving
FIDO2 / WebAuthn High (origin-bound) Security key or platform authenticator High High-assurance scenarios: banking, admin access, NIST AAL3

Decision rule: default to TOTP for most applications — it is widely supported, requires no hardware, and is significantly more secure than SMS. Use FIDO2 when phishing resistance is a hard requirement (financial services, privileged access). Avoid SMS OTP for new systems; it is the weakest 2FA method and vulnerable to SIM swapping.

Questions

References


Whats next