# MFA Options — Authentication & Authorization

Source: https://www.skillbyai.com/en/authentication/m-mfa

> TOTP, push, SMS and their weaknesses.

## Not all second factors are equal

**TOTP** (RFC 6238) derives a short code from a shared secret and the current time step (commonly 30 seconds, 6 digits) in an authenticator app; it works offline but can be phished in real time. **Push approval** is convenient but invites **MFA fatigue**: attackers spam prompts until a user taps approve; **number matching** (typing a number shown on the login screen) reduces this. **SMS and voice codes** are the weakest common option: vulnerable to SIM swapping, number porting and interception, and NIST SP 800-63B treats SMS as a restricted authenticator. **Security keys and passkeys** (WebAuthn) are phishing-resistant. Offer **recovery codes** generated once, stored hashed and single use, and treat MFA enrolment and removal as sensitive actions requiring re-authentication.

## Beyond the password

Extra factors and public-key credentials stop most account takeover; federated login brings its own pitfalls.

![Three ideas: MFA options, passkeys and WebAuthn, social login and account linking.](assets/figures/authentication/section-6-map.svg) — Figure 6.1 — Factor, passkey, federation.

## TOTP enrolment and verification

Using pyotp; store the secret encrypted, and prevent code reuse within the window.

```python
import pyotp

def start_enrolment(user):
    secret = pyotp.random_base32()
    db.mfa.save_pending(user.id, encrypt(secret))
    uri = pyotp.TOTP(secret).provisioning_uri(name=user.email, issuer_name='ExampleApp')
    return uri  # render as a QR code for the authenticator app

def verify_code(user, code: str) -> bool:
    secret = decrypt(db.mfa.get_secret(user.id))
    totp = pyotp.TOTP(secret)
    if not totp.verify(code, valid_window=1):     # allow +/- one 30s step of drift
        return False
    if db.mfa.code_used_recently(user.id, code):  # block replay inside the window
        return False
    db.mfa.mark_used(user.id, code)
    return True
```

## Rate limit the second factor too

A 6-digit code has only a million possibilities. Without attempt limits on the MFA step, attackers who already hold the password can brute-force it.

**Quiz:** Why is SMS considered a weak second factor?

- [ ] SMS cannot be rate limited
- [ ] SMS codes are always longer than TOTP codes
- [x] Codes can be stolen through SIM swapping or interception
- [ ] SMS requires the user to know a password

*Answer:* Codes can be stolen through SIM swapping or interception. Phone numbers can be hijacked without touching the user's device.
