Get AI summaries of any video or article — Sign up free
Python FastAPI Tutorial (Part 14): Password Reset - Email, Tokens, and Background Tasks thumbnail

Python FastAPI Tutorial (Part 14): Password Reset - Email, Tokens, and Background Tasks

Corey Schafer·
5 min read

Based on Corey Schafer's video on YouTube. If you like this content, support the original creators by watching, liking and subscribing to their content.

TL;DR

Reset tokens must be unguessable, short-lived (default 60 minutes), and single-use, enforced by deleting tokens after use and when new tokens are requested.

Briefing

A complete password reset system for a FastAPI blog app comes together by combining three security requirements—unguessable, expiring, single-use reset tokens—with non-blocking email delivery. The flow is designed so attackers can’t learn which email addresses exist in the system, and it avoids tying reset validity to JWTs that can’t be cleanly invalidated without extra infrastructure.

The implementation starts with async email sending. Because FastAPI runs on an async event loop, the tutorial installs AIO SMTP lib (instead of Python’s built-in synchronous SMTP) and wires it to configuration stored in Pydantic settings. Email credentials and SMTP details live in an uncommitted .env file, with TLS enabled and a dedicated front-end base URL used to build reset links. For development, Mail Trap provides an email sandbox so test messages are captured safely rather than sent to real inboxes.

Reset tokens are handled as database-backed secrets. A new SQLAlchemy model, password reset tokens, stores token_hash (a SHA-256 hash) rather than the raw token, along with expires_at and created_at. When a user requests a reset, any existing tokens for that user are deleted, then a new random token is generated using secrets.token_urlsafe(32), hashed, stored with an expiration time (default 60 minutes), and emailed. The raw token is what goes into the reset URL; the database only ever sees the hash.

Email delivery is pushed into FastAPI BackgroundTasks so the API responds immediately with a 202 Accepted message. That endpoint deliberately returns the same generic response whether or not the email exists, preventing email enumeration attacks. If the user clicks the reset link, the reset password endpoint hashes the submitted token, looks up the matching token hash in the database, checks expires_at (including a SQLAlchemy time zone workaround for SQLite), and rejects invalid or expired tokens with a generic error. On success, it updates the user’s password hash, deletes all outstanding reset tokens for that user (invalidating any other links), and does not auto-login—forcing a normal login afterward.

For logged-in users, a change-password endpoint verifies the current password before allowing an update, then deletes any outstanding reset tokens to keep the account consistent. To make the system usable in a browser, the tutorial adds front-end routes and templates for forgot password and reset password pages. The reset page extracts the token from the query string, submits the new password to the reset endpoint, and includes a referer-suppression measure (referrerPolicy set to no-referrer) to reduce token leakage via browser headers.

End-to-end testing confirms the backend endpoints and the UI layer work together: requesting a reset yields a Mail Trap email with a working tokenized link, resetting via the API updates credentials, and changing passwords from the account page succeeds. The tutorial closes by noting the next step—moving from SQLite to Postgres and using Olympic for migrations—so the same token logic can run in a production-ready database setup.

Cornell Notes

The password reset design uses random, single-use tokens stored as SHA-256 hashes in the database, with a short expiration window (default 60 minutes). A “forgot password” request always returns the same 202 Accepted message to avoid email enumeration, while email sending runs asynchronously via FastAPI BackgroundTasks. When a reset link is used, the API hashes the submitted token, verifies it exists and hasn’t expired, updates the user’s password hash, and deletes all reset tokens for that user to invalidate any other links. Logged-in users can change passwords only after providing the correct current password, and that action also clears outstanding reset tokens. Front-end templates provide a browser-friendly flow that calls the same API endpoints.

Why store a hash of the reset token in the database instead of the token itself?

The password reset tokens model stores token_hash (64-character SHA-256 hex digest) rather than the raw token. If the database is exposed, attackers get only hashes that can’t be used without the original token. During reset, the submitted raw token is hashed with the same SHA-256 function and matched against token_hash, preserving security while still enabling verification.

How does the system prevent attackers from learning which emails are registered?

The forgot password endpoint returns a generic 202 Accepted response regardless of whether the email exists. It does not reveal “email not found” or any other differentiator. Even when the user exists, the response message stays the same; only the background email sending differs internally.

What makes the reset token “single use” in this design?

After a successful reset, the API deletes reset tokens for the user (not just the one token). That means any previously issued reset links become invalid immediately. Additionally, when a new reset is requested, existing tokens for that user are deleted, so older links stop working even before the new one is used.

Why use BackgroundTasks for password reset emails, and what limitation comes with it?

Sending emails takes time and involves network round trips, so BackgroundTasks lets the API return a response immediately and send the email after the response is sent. The tradeoff is reliability: if the server crashes, pending background tasks are lost. The tutorial notes that for critical workloads a queue like Celery would be more appropriate, but for password resets users can request another link.

How does the reset endpoint handle token expiration with SQLite time zone quirks?

The endpoint checks reset_token.expires_at and compares it to the current UTC time. Because SQLite strips time zone information even when time zone is declared, the code uses replace(tzinfo=UTC) to reattach time zone info for comparison. The tutorial flags that this workaround won’t be needed after switching to Postgres.

What security measure is added to the reset-password front-end page regarding the token in the URL?

The reset password route sets referrerPolicy to no-referrer. Since the token is in the query string, a browser could otherwise send the full URL (including the token) in the Referer header when navigating. Suppressing referrer headers reduces the chance of token leakage.

Review Questions

  1. Walk through the exact steps taken from a forgot password request to a successful password reset, including which values are hashed and when tokens are deleted.
  2. What responses does the forgot password endpoint return, and how do those choices mitigate email enumeration attacks?
  3. How would you modify the design if you needed stronger guarantees than BackgroundTasks provides when the server restarts?

Key Points

  1. 1

    Reset tokens must be unguessable, short-lived (default 60 minutes), and single-use, enforced by deleting tokens after use and when new tokens are requested.

  2. 2

    Store only token hashes (SHA-256) in the database; send the raw token only inside the email link.

  3. 3

    Return a uniform 202 Accepted message for forgot-password requests to prevent email enumeration.

  4. 4

    Send reset emails asynchronously using FastAPI BackgroundTasks so API responses aren’t blocked by SMTP round trips.

  5. 5

    Validate reset tokens by hashing the submitted token, looking up token_hash, and checking expires_at (with a SQLite time zone workaround).

  6. 6

    After a successful reset, update the user password hash and delete all outstanding reset tokens for that user; do not auto-login.

  7. 7

    Provide browser-friendly templates that call the same API endpoints, including referrer suppression to reduce token leakage.

Highlights

The reset flow uses database-backed, single-use tokens: a random token is emailed, but only its SHA-256 hash is stored and later verified.
The forgot-password endpoint always returns 202 Accepted with the same message, blocking email enumeration attempts.
BackgroundTasks sends emails after the response is returned, keeping the async FastAPI event loop responsive.
The reset-password front-end sets referrerPolicy to no-referrer to reduce the risk of token exposure via Referer headers.

Topics

  • Password Reset
  • Async Email
  • Token Security
  • FastAPI BackgroundTasks
  • SQLAlchemy Models

Mentioned

  • Mail Trap
  • AIO SMTP lib
  • JWT
  • UTC
  • TLS
  • SMTP