Python FastAPI Tutorial (Part 10): Authentication - Registration and Login with JWT
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.
Install Argon 2 support, a JWT library, and Pydantic Settings to implement secure authentication and configuration management.
Briefing
Authentication is wired into the FastAPI app by adding secure password hashing (Argon 2), JWT access tokens, and backend endpoints for registration, login, and “who am I?”—so the system can reliably identify users instead of relying on a hard-coded user ID. The change matters because it lays the foundation for real security: once tokens can be issued and validated, routes can later be protected so only the right users can access or modify data.
The build starts with new dependencies: Argon 2 support for password hashing, a JWT library for token creation/verification, and Pydantic Settings for configuration management. Argon 2 is chosen over BCrypt as the more modern, GPU-resistant approach. Configuration is centralized in a settings module that reads secrets (notably the JWT signing secret) from an .env file or environment variables, with type validation and “fail fast” behavior. A secret key is generated using Python’s secrets module and .env is kept out of Git via gitignore.
Because SQLite makes schema changes awkward, the existing database is deleted to start fresh. The user model is updated by adding a non-nullable password hash field (explicitly “password_hash,” never a plaintext password). On the API schema side, registration requires a password with a minimum length of 8 characters. Response models are split to improve privacy: public user data used in post responses omits email, while a private user schema includes email only when appropriate. A token response schema is also added for login.
Authentication utilities are then created. A password hasher is configured with PWD lib’s Argon 2 “recommended” settings. JWT handling is implemented so tokens include an expiration and store the user ID in the JWT “sub” claim. Verification returns the user ID when the token is valid and not expired; otherwise it yields none. An OAuth2PasswordBearer scheme is configured with a token URL that matches the login endpoint, enabling FastAPI’s built-in “Authorize” workflow in the docs.
User routes are updated accordingly. Registration hashes the password before saving, normalizes email to lowercase, and performs case-insensitive uniqueness checks for both username and email (using SQLAlchemy’s lower() approach). Login accepts form-encoded credentials via OAuth2PasswordRequestForm (using the form field named “username” to carry the email), looks up the user case-insensitively, verifies the password, and returns a JWT access token with token type “bearer.” Importantly, incorrect email and incorrect password produce the same generic 401 error to avoid leaking which accounts exist.
A new /me endpoint is added to fetch the current authenticated user. It extracts the bearer token from the Authorization header, verifies it, converts the subject to an integer defensively, and returns 401 for invalid/expired tokens or missing users. This endpoint gives the front end a server-validated way to determine login state.
Finally, the front end gains registration and login templates. Login posts form data to the token endpoint, stores the returned access token in localStorage, and uses a small JS module to fetch /me, cache the current user, and clear tokens on logout. The navbar updates based on whether a current user is available. Testing confirms registration, login, token storage, and /me validation work.
What remains intentionally unprotected: post creation/edit/delete still uses the old hard-coded user ID in the post flow, so anyone can modify posts for now. The next tutorial is positioned to replace those hard-coded IDs with the authenticated user ID and enforce ownership checks—turning authentication into actual authorization.
Cornell Notes
The app adds a full authentication layer: users register with a password that gets hashed using Argon 2, then log in to receive a JWT access token. The JWT stores the user ID in the “sub” claim and includes an expiration; invalid or expired tokens fail verification. A /me endpoint validates the bearer token and returns the current user, enabling the front end to confirm login state without trusting client-side decoding. Privacy is improved by splitting user response schemas so public responses omit email while private responses include it. These pieces set up the next step—protecting routes and enforcing what authenticated users are allowed to do.
Why is plaintext password storage avoided, and how is the password handled instead?
How does the system decide whether a JWT is valid, and what data does it carry?
What security measure prevents attackers from learning whether an email exists?
Why split user response schemas into public and private?
How does the /me endpoint work, and why is it useful for the front end?
What does the front end do with the access token, and what are the tradeoffs?
Review Questions
- What fields and normalization rules are applied to username and email during registration to enforce uniqueness safely?
- Describe the JWT claims used and the failure behavior when a token is expired or invalid.
- Why does the app return a generic 401 error for both “email not found” and “wrong password” cases?
Key Points
- 1
Install Argon 2 support, a JWT library, and Pydantic Settings to implement secure authentication and configuration management.
- 2
Add password_hash to the user model and hash passwords with Argon 2; never store plaintext passwords.
- 3
Use separate user schemas (public vs private) so post responses omit email while authenticated “current user” responses can include it.
- 4
Create JWT access tokens that sign with a secret key and store the user ID in the sub claim with an expiration time.
- 5
Implement registration and login endpoints with case-insensitive uniqueness checks and generic 401 errors to avoid account enumeration.
- 6
Add a /me endpoint that validates bearer tokens server-side and returns the authenticated user or 401 on failure.
- 7
Update the front end with registration/login forms, token storage, and an authorization-state module that caches current user data and clears tokens on logout.