A from-scratch Flask authentication service: JWT access + refresh tokens with rotation and reuse detection, real session/device revocation, password reset, email verification, and TOTP 2FA with backup codes.
Nobody has started this yet — be first.
No published tickets in this project yet.
About this project
A from-scratch Flask auth service covering signup/login, JWT access + refresh tokens with rotation and reuse detection, session ("logged-in devices") management with real revocation, password reset, email verification, and TOTP 2FA with backup codes. This is deliberately not "add JWT to an app" -- it is built around the handful of details that separate a real auth system from a tutorial one: refresh tokens are one-time use, and replaying an already-used one kills the entire session family, not just that one token; logging out (or resetting a password) actually invalidates already-issued access tokens immediately via a Redis denylist keyed by JWT jti, not just future ones, even though access tokens are stateless JWTs chosen specifically for fast verification; a TOTP code cannot be submitted twice, even though it is technically still "valid" for the rest of its 30-second window, by tracking the last-consumed step per user; and login, password-reset-request, and email-verification-resend are all rate limited on two independent axes (client IP and target account), so neither a distributed attack spread across many IPs at one account nor a single IP hammering many accounts slips through. Two hashing algorithms are used on purpose -- argon2id for human-chosen passwords, fast SHA-256 for high-entropy server-generated tokens that get rehashed on every request that presents them.
Clone it
$git clone https://github.com/nishant1821/FlaskDevTraining.gitHow to run it locally, step by step.
Fifteen practice tickets are injected into an otherwise-working codebase: ten are real, minimal, single-mistake bugs (an inverted <=/< boundary on a password-length check, an off-by-one loop bound that silently generates one fewer backup code than promised, an inverted TLS boolean that sends password-reset emails over plaintext SMTP, a min/max mix-up that reports a Retry-After of zero, a missing cleanup statement that leaves old 2FA backup codes valid forever, a classic IDOR with no ownership check on session revocation, a genuine asyncio-free-but-still-real SQLAlchemy race with no WHERE-guarded update in refresh-token rotation, and a reordered pair of checks that silently disables the whole project's headline reuse-detection security property), and five are half-built enhancements -- a fully-wired stub endpoint that always 501s, an already-scaffolded extension point (account lockout, audit logging) with every function a no-op, and a config value (TOTP_DIGITS, REMEMBER_ME_REFRESH_TOKEN_TTL_DAYS) read correctly on one side of a feature but ignored on the other -- the smallest thing that compiles, never a disguised version of the real fix. Ordered easy to hard, each ticket has its own currently-failing pytest test that goes green when (and only when) that ticket is fixed; no solutions are given anywhere in the ticket docs. Nine of the ten injected bugs are real enough that they also break tests in the project's own main suite (as of writing: tickets 02, 03, 07, 11, and 15) -- expected, not a second problem to chase separately, since fixing the ticket fixes both. The hardest ticket, TICKET-15, is a genuine, security-critical logic-ordering bug in refresh-token reuse detection: because a rotated token always has both revoked_at and replaced_by set, checking revoked_at first makes the replaced_by-driven family-kill branch permanently dead code, so replaying a stolen, already-rotated refresh token is silently treated as "just revoked" instead of "compromise detected" -- every surface-level check (the replay itself gets rejected) still passes, and only checking whether the successor token also died reveals the regression.