Python — FastAPI Development

Password Reset Flow with FastAPI and React Native

Direct answer

A secure password reset issues a single-use, short-lived token, emails it as a deep link into your app, and lets the user set a new password without ever revealing whether the email exists. Store the token hashed (like a refresh token), expire it in about 30 minutes, mark it used the moment it succeeds, and revoke the user's existing sessions so a stolen-then-reset account does not stay logged in elsewhere. The two mistakes that turn this feature into a vulnerability are leaking which emails are registered and letting a reset token be replayed.

Password reset is the auth flow attackers probe first, because a sloppy one leaks accounts or hands them over. It is also where React Native adds a wrinkle: the reset link in the email has to open your app, not a website. Here is a flow that closes both gaps — safe on the FastAPI side, and deep-linked cleanly on the Expo side.

Key facts, with sources

  • In the JetBrains Python Developers Survey 2024, which collected responses from more than 30,000 Python developers, FastAPI usage jumped from 29% to 38%, overtaking Django (35%) and Flask (34%) as the most-used Python web framework. (JetBrains Python Developers Survey 2024)
  • The 2025 Stack Overflow Developer Survey shows FastAPI at 14.8% of respondents doing extensive work with it, edging out Flask at 14.4% and Django at 12.6%. (Stack Overflow Developer Survey 2025)
  • FastAPI's official documentation cites independent TechEmpower benchmarks showing FastAPI applications running under Uvicorn as one of the fastest Python frameworks available, ranked only below Starlette and Uvicorn themselves. (FastAPI official documentation)
  • FastAPI surpassed Flask in GitHub stars in December 2025, reaching roughly 88,000 stars compared to Flask's 68,400. (DZone)
  • Industry analysis of FastAPI's 2025 growth reports about 40% year-over-year growth in job mentions and production adoption at companies including Uber, Netflix, and Microsoft. (byteiota)

Never reveal whether the email exists

The request-reset endpoint must return the same response whether or not the email is registered. If a registered address returns 200 and an unknown one returns 404, you have handed an attacker a way to enumerate your entire user base. Do the work only when the user exists, but always answer with the same neutral message.

auth_reset.py
import secrets, hashlib
from datetime import datetime, timedelta, timezone

def _hash(raw: str) -> str:
    return hashlib.sha256(raw.encode()).hexdigest()

@router.post("/auth/forgot-password")
def forgot_password(body: ForgotIn, db: Session = Depends(get_db)):
    user = db.query(User).filter_by(email=body.email).first()

    # Only act if the user exists — but ALWAYS return the same response.
    if user:
        raw = secrets.token_urlsafe(32)
        db.add(PasswordReset(
            user_id=user.id,
            token_hash=_hash(raw),                 # store the hash, not the token
            expires_at=datetime.now(timezone.utc) + timedelta(minutes=30),
        ))
        db.commit()
        send_reset_email(user.email, raw)          # raw token goes only to the inbox

    return {"message": "If that email exists, a reset link is on its way."}

Make the token single-use and revoke old sessions

The confirm endpoint validates the token, rejects anything expired or already used, sets the new password, and marks the token used so it cannot be replayed. Then revoke the user's existing refresh tokens — a password reset should log out every other device, because the whole point may be that one was compromised.

auth_reset.py (continued)
@router.post("/auth/reset-password")
def reset_password(body: ResetIn, db: Session = Depends(get_db)):
    row = db.query(PasswordReset).filter_by(token_hash=_hash(body.token)).first()
    if row is None or row.used or row.expires_at < datetime.now(timezone.utc):
        raise HTTPException(400, "invalid or expired reset token")

    user = db.query(User).get(row.user_id)
    user.password_hash = hash_password(body.new_password)
    row.used = True                                # single-use: no replay

    # A reset ends every other session — revoke all refresh-token families.
    db.query(RefreshToken).filter_by(user_id=user.id).update({"revoked": True})
    db.commit()
    return {"message": "Password updated. Please sign in."}

Deep-link the reset email into the Expo app

The link in the email should open your app straight to the reset screen with the token in hand. With Expo Router, configure your scheme and let the reset route read the token from params. Keep a web fallback URL too, for users who open the mail on a device without the app installed.

app/reset-password.tsx
import { useLocalSearchParams, router } from "expo-router";
import { api } from "../lib/api";

// Email link: https://yourapp.com/reset-password?token=... (Universal Link),
// or yourapp://reset-password?token=... via the custom scheme.
export default function ResetPassword() {
  const { token } = useLocalSearchParams<{ token: string }>();

  async function submit(newPassword: string) {
    await api.post("/auth/reset-password", { token, new_password: newPassword });
    router.replace("/login");
  }
  // ...render the new-password form and call submit()
}

When to hire senior help

Bring in senior help when your API needs to handle real concurrency, when you are designing service boundaries and auth for the first time, or when an existing FastAPI codebase mixes sync and async code and latency is degrading. An experienced engineer can usually diagnose event-loop blocking and connection-pool misconfiguration in days, which is far cheaper than re-architecting after launch. If your stack includes React Native + Python + AI, a senior engineer who owns the full product beats coordinating multiple juniors.

Bottom line

Dhairya Senjaliya ships Python — FastAPI Development projects worldwide — book a scoping call to discuss your specific situation.

Common pitfalls to avoid

  • Calling blocking libraries (classic SQLAlchemy sessions, requests, heavy file I/O) inside async def endpoints, which stalls the event loop and erases FastAPI's concurrency advantage
  • Deploying a single Uvicorn process with no process manager or worker scaling, leaving most CPU cores idle under production load
  • Treating the auto-generated OpenAPI docs as a versioning strategy, then breaking mobile and partner clients when response schemas change
  • Running large payloads through deeply nested Pydantic models on every request and response, adding serialization latency that shows up only at scale

Frequently asked questions

Why not just email the new password?

Because it means you either know or can recover the plaintext password, which you never should, and email is not a secure channel. Always email a single-use reset token instead, and let the user choose a new password that you immediately hash.

How long should the reset token live?

Short — around 15 to 30 minutes. Long enough for a user to act on the email, short enough that a leaked link goes stale fast. Pair the short TTL with single-use invalidation so a token cannot be replayed even within the window.

Should a password reset log the user out everywhere?

Yes. Revoke all existing refresh tokens on reset. If the reset was triggered because an account was compromised, leaving other sessions alive defeats the purpose.

Is FastAPI mature enough for production?

Yes. It was the most-used Python web framework in the JetBrains 2024 survey at 38%, and companies including Uber, Netflix, and Microsoft run it in production. The ecosystem for auth, ORMs, and testing is now well established.

How much faster is FastAPI than Flask or Django really?

Independent TechEmpower benchmarks place FastAPI among the fastest Python frameworks, and published comparisons show several times Flask's throughput on I/O-bound endpoints. For CPU-bound work or database-bottlenecked apps, the framework choice matters far less than query and infrastructure design.

Should we pick FastAPI or Django for a new SaaS backend?

FastAPI suits API-first products, microservices, and ML model serving because of async support and automatic OpenAPI docs. Django ships batteries included (admin, ORM, auth) and is often faster to launch a conventional CRUD product. Many teams run both, per the JetBrains finding that a third of Django developers also use Flask or FastAPI.

Bottom line: Dhairya Senjaliya ships Python — FastAPI Development projects worldwide. Book a scoping call at https://dhairyasenjaliya.com/#book-call.

Sources

Related guides

Keep up with new guides

New deep-dive guides on React Native, Python, and AI ship regularly. Subscribe via RSS or follow on LinkedIn.

Want help implementing this?

30-minute scoping call · Clear milestones · Senior engineer ownership