Skip to content

fix(mfa): prevent TOTP replay attacks by caching used one-time codes in Redis until expiration window #1385

Description

@Pcmhacker-hero

Description

During multi-factor authentication (MFA) verification in backend/app/routers/mfa.py, the server verifies whether the 6-digit TOTP code matches the current 30-second time step. Because the server allows a +/- 1 time step drift window and does not record used tokens, an intercepted TOTP code can be replayed multiple times within the 90-second validity window.

Steps to Reproduce

  1. Generate a valid 6-digit TOTP code.
  2. Send POST /api/v1/auth/mfa/verify with the code. Verification succeeds.
  3. Immediately send the exact same code in a second request within 15 seconds.
  4. Second request succeeds instead of being rejected as already consumed.

Expected Behavior

  • Backend: When a TOTP code is verified successfully, store mfa_used:{user_id}:{totp_code} in Redis with an expiration of 90 seconds. Reject any incoming verification matching an active cached key.

Implementation Hints

Backend (backend/app/routers/mfa.py):

@router.post("/verify", response_model=TokenResponse)
async def verify_mfa_code(
    payload: MFAVerifyRequest,
    db: AsyncSession = Depends(get_db),
    redis: Redis = Depends(get_redis)
):
    cache_key = f"mfa_used:{payload.user_id}:{payload.code}"
    if await redis.exists(cache_key):
        raise HTTPException(status_code=400, detail="MFA code has already been used. Please wait for the next token.")

    is_valid = verify_totp(payload.user_id, payload.code)
    if not is_valid:
        raise HTTPException(status_code=400, detail="Invalid MFA code")

    await redis.setex(cache_key, 90, "1")
    return generate_auth_session(payload.user_id)

Affected Files

  • backend/app/routers/mfa.py
  • backend/app/core/security.py
  • backend/app/core/cache.py

Labels
type:bug, level:advanced, GSSoC-26

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions