Get AI summaries of any video or article — Sign up free
Python FastAPI Tutorial (Part 11): Authorization - Protecting Routes and Verifying Current User thumbnail

Python FastAPI Tutorial (Part 11): Authorization - Protecting Routes and Verifying Current User

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

Create a reusable get current user dependency that validates JWTs and loads the authenticated user from the database, raising 401 Unauthorized on any failure.

Briefing

Authorization becomes real only after the API stops trusting client-supplied user IDs. This tutorial turns a working JWT login system into route protection by introducing a reusable “current user” dependency that validates the token, loads the user from the database, and injects the authenticated user into any endpoint that needs it. The payoff is immediate: post creation no longer lets clients impersonate other users, and edit/delete operations gain strict ownership rules.

The starting point is a security gap. The post creation schema still accepts a user_id from the request body, and the front end uses hard-coded user IDs. That means anyone can create, edit, or delete posts for any account simply by changing IDs. The fix is to remove user_id from the client payload and instead derive it from the trusted JWT. A new dependency is built by combining existing pieces: an OAuth2 token extractor that reads the Authorization header and a verify_access_token function that checks validity and returns a user identifier. The dependency then converts the token’s user ID to an integer, queries the database for the corresponding user, and raises a 401 Unauthorized when the token is missing/invalid/expired or when the user no longer exists.

To make this ergonomic, the tutorial defines a type alias (current user) using FastAPI’s annotated dependency pattern. Routes can then declare current user as a parameter, and FastAPI automatically enforces authentication before the endpoint logic runs. With that in place, the postcreate schema drops the user_id field entirely, and the post creation endpoint assigns post.user_id from current_user.id. The code also shrinks because the manual “verify user exists” logic is no longer needed.

Ownership checks tighten the remaining holes. For update and delete endpoints, the tutorial adds the current user dependency and then compares the post’s stored owner ID to current_user.id. If they don’t match, the API returns 403 Forbidden with a clear “not authorized” message—distinguishing “not authenticated” (401) from “authenticated but not permitted” (403). The same pattern is applied to user profile update and user deletion, ensuring users can only modify or remove their own accounts.

A major usability upgrade follows on the front end. API calls now include the Authorization: Bearer <token> header, and client-side handlers redirect to login on 401. Edit/delete buttons on post pages are hidden unless the logged-in user owns the post, with JavaScript performing the ownership comparison after fetching the current user. The tutorial also adds an account page that fetches the current user via the /me endpoint and supports profile updates and account deletion. Importantly, the account page itself isn’t server-protected because the server can’t read localStorage tokens during template rendering; real security still lives in the API, where missing/invalid tokens trigger 401 and ownership mismatches trigger 403.

End-to-end testing confirms the behavior: unauthenticated requests to protected routes fail, posts created by one user can’t be edited or deleted by another, and deleting an account removes that user’s posts. The result is a production-style authorization layer built directly on top of JWT authentication—no more hard-coded IDs, and no more client-controlled identity.

Cornell Notes

The tutorial upgrades a JWT login system into real authorization by adding a reusable FastAPI dependency that returns the authenticated user. That dependency extracts the token from the Authorization header, verifies it, converts the token user ID, queries the database for the user, and raises 401 Unauthorized on any failure. Routes then declare a current user parameter so authentication happens automatically before endpoint code runs. Post creation stops accepting user_id from the request body and instead assigns ownership from the token-derived user. Update and delete endpoints add ownership checks that return 403 Forbidden when the authenticated user doesn’t own the target resource. The front end is updated to send Authorization headers and to hide edit/delete controls unless ownership matches.

Why is accepting user_id in the post creation request body a security problem, and how does the tutorial fix it?

If the client sends user_id in the request body, anyone can impersonate another user by changing that value. The fix removes user_id from the postcreate schema so the client can only send title/content. The server then derives the user identity from the JWT: a get current user dependency verifies the token and loads the user from the database, and the post endpoint sets post.user_id from current_user.id.

What does the reusable get current user dependency do, step by step, and what errors does it raise?

It takes the token from the Authorization header (via the OAuth2 scheme) and a database session. It calls verify_access_token; if the token is invalid/expired (returns None), it raises 401 Unauthorized. It converts the token’s user ID from string to integer; if conversion fails, it raises 401. It queries the database for the user; if no user exists (e.g., deleted account), it raises 401. On success, it returns the full user object.

How does the tutorial distinguish between authentication failures and authorization failures?

Authentication problems use 401 Unauthorized—typically missing, invalid, or expired tokens. Authorization problems use 403 Forbidden—when a valid logged-in user attempts an action they don’t have permission for. Ownership checks on update/delete use 403 when post.user_id doesn’t match current_user.id, which signals “you’re logged in, but not allowed.”

How do ownership checks work for editing and deleting posts?

After confirming the post exists, the endpoint compares post.user_id to current_user.id. If they differ, it raises an HTTPException with status code 403 and a message like “not authorized to update this post” (or delete). This prevents users from editing or deleting posts they don’t own, even if they know the post ID.

Why isn’t the account page protected on the server the same way as API routes?

The JWT is stored in localStorage and is only accessible to JavaScript in the browser. When a browser requests /account, it doesn’t automatically include the token, so the server can’t reliably know who’s logged in during template rendering. Instead, JavaScript fetches the current user from /me and redirects to login if not authenticated. Real security still comes from the API endpoints, which require valid tokens and enforce ownership.

What front-end changes are required for the protected API to work?

Client requests must include the Authorization header: Authorization: Bearer <token>. Form handlers add a token check (redirect to login if no token), handle 401 by redirecting to login when the token is invalid/expired, and handle 403 by showing an error modal for permission issues. UI controls like edit/delete buttons are hidden unless JavaScript confirms ownership by comparing the current user to the post owner.

Review Questions

  1. What specific fields are removed from the post creation request, and where does the server get the post owner ID instead?
  2. Why does the tutorial use 403 Forbidden for ownership mismatches rather than 401 Unauthorized?
  3. How does the /me endpoint relate to the current user dependency and the front-end account page behavior?

Key Points

  1. 1

    Create a reusable get current user dependency that validates JWTs and loads the authenticated user from the database, raising 401 Unauthorized on any failure.

  2. 2

    Remove user_id from the post creation request body so clients can’t impersonate other users; assign post.user_id from current_user.id.

  3. 3

    Protect endpoints by adding the current user dependency parameter to route signatures so authentication happens before endpoint logic runs.

  4. 4

    Implement ownership checks for update and delete: compare post.user_id to current_user.id and return 403 Forbidden when they don’t match.

  5. 5

    Update front-end API calls to send Authorization: Bearer <token> and handle 401 by redirecting to login.

  6. 6

    Hide edit/delete UI controls on the front end unless the logged-in user owns the post, but rely on the backend for real security.

  7. 7

    Use client-side guarding for the account page (since tokens live in localStorage) while keeping backend authorization as the enforcement layer.

Highlights

A single reusable dependency turns token verification and user lookup into a one-line route parameter, eliminating repeated authentication boilerplate.
Post creation becomes secure by removing client-supplied user_id entirely and assigning ownership from the JWT-derived current user.
Ownership failures return 403 Forbidden (authenticated but not permitted), while missing/invalid tokens return 401 Unauthorized (not authenticated).
The account page uses JavaScript to check login state because server-side template rendering can’t access localStorage tokens; the API still enforces security.

Topics

  • JWT Authorization
  • FastAPI Dependencies
  • Route Protection
  • Ownership Checks
  • Front-End Authorization Headers

Mentioned