User authentication with FastAPI

Use the current user in the API router

Want more?

This lesson for enrolled students only. Join the course to unlock it!

You can see the code changes implemented in this lecture below.

If you have purchased the course in a different platform, you still have access to the code changes per lecture here on Teclado. The lecture video and lecture notes remain locked.
Join course for $30

Modified files

storeapi/security.py
--- 
+++ 
@@ -2,6 +2,7 @@
 import logging

 from fastapi import HTTPException, status
+from fastapi.security import OAuth2PasswordBearer
 from jose import ExpiredSignatureError, JWTError, jwt
 from passlib.context import CryptContext
 from storeapi.database import database, user_table
@@ -11,6 +12,7 @@
 SECRET_KEY = "9b73f2a1bdd7ae163444473d29a6885ffa22ab26117068f72a5a56a74d12d1fc"
 ALGORITHM = "HS256"

+oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
 pwd_context = CryptContext(schemes=["bcrypt"])

 credentials_exception = HTTPException(
storeapi/routers/post.py
--- 
+++ 
@@ -1,6 +1,6 @@
 import logging

-from fastapi import APIRouter, HTTPException
+from fastapi import APIRouter, HTTPException, Request
 from storeapi.database import comment_table, database, post_table
 from storeapi.models.post import (
     Comment,
@@ -9,6 +9,8 @@
     UserPostIn,
     UserPostWithComments,
 )
+from storeapi.models.user import User
+from storeapi.security import get_current_user, oauth2_scheme

 router = APIRouter()

@@ -26,8 +28,11 @@


 @router.post("/post", response_model=UserPost, status_code=201)
-async def create_post(post: UserPostIn):
+async def create_post(post: UserPostIn, request: Request):
     logger.info("Creating post")
+    current_user: User = await get_current_user(
+        await oauth2_scheme(request)
+    )  # noqa: F841

     data = post.model_dump()  # previously .dict()
     query = post_table.insert().values(data)
@@ -50,8 +55,11 @@


 @router.post("/comment", response_model=Comment, status_code=201)
-async def create_comment(comment: CommentIn):
+async def create_comment(comment: CommentIn, request: Request):
     logger.info("Creating comment")
+    current_user: User = await get_current_user(
+        await oauth2_scheme(request)
+    )  # noqa: F841

     post = await find_post(comment.post_id)
storeapi/tests/conftest.py
--- 
+++ 
@@ -41,3 +41,9 @@
     user = await database.fetch_one(query)
     user_details["id"] = user.id
     return user_details
+
+
+@pytest.fixture()
+async def logged_in_token(async_client: AsyncClient, registered_user: dict):
+    response = await async_client.post("/token", json=registered_user)
+    return response.json()["access_token"]
storeapi/tests/routers/test_post.py
--- 
+++ 
@@ -1,43 +1,81 @@
 import pytest
 from httpx import AsyncClient
+from storeapi import security


-async def create_post(body: str, async_client: AsyncClient) -> dict:
-    response = await async_client.post("/post", json={"body": body})
+async def create_post(
+    body: str, async_client: AsyncClient, logged_in_token: str
+) -> dict:
+    response = await async_client.post(
+        "/post",
+        json={"body": body},
+        headers={"Authorization": f"Bearer {logged_in_token}"},
+    )
     return response.json()


-async def create_comment(body: str, post_id: int, async_client: AsyncClient) -> dict:
+async def create_comment(
+    body: str, post_id: int, async_client: AsyncClient, logged_in_token: str
+) -> dict:
     response = await async_client.post(
-        "/comment", json={"body": body, "post_id": post_id}
+        "/comment",
+        json={"body": body, "post_id": post_id},
+        headers={"Authorization": f"Bearer {logged_in_token}"},
     )
     return response.json()


 @pytest.fixture()
-async def created_post(async_client: AsyncClient):
-    return await create_post("Test Post", async_client)
+async def created_post(async_client: AsyncClient, logged_in_token: str):
+    return await create_post("Test Post", async_client, logged_in_token)


 @pytest.fixture()
-async def created_comment(async_client: AsyncClient, created_post: dict):
-    return await create_comment("Test Comment", created_post["id"], async_client)
+async def created_comment(
+    async_client: AsyncClient, created_post: dict, logged_in_token: str
+):
+    return await create_comment(
+        "Test Comment", created_post["id"], async_client, logged_in_token
+    )


 @pytest.mark.anyio
-async def test_create_post(async_client: AsyncClient):
+async def test_create_post(async_client: AsyncClient, logged_in_token: str):
     body = "Test Post"

-    response = await async_client.post("/post", json={"body": body})
-
+    response = await async_client.post(
+        "/post",
+        json={"body": body},
+        headers={"Authorization": f"Bearer {logged_in_token}"},
+    )
     assert response.status_code == 201
     assert {"id": 1, "body": body}.items() <= response.json().items()


 @pytest.mark.anyio
-async def test_create_post_missing_data(async_client: AsyncClient):
-    response = await async_client.post("/post", json={})
+async def test_create_post_expired_token(
+    async_client: AsyncClient, registered_user: dict, mocker
+):
+    mocker.patch("storeapi.security.access_token_expire_minutes", return_value=-1)
+    token = security.create_access_token(registered_user["email"])
+    response = await async_client.post(
+        "/post",
+        json={"body": "Test Post"},
+        headers={"Authorization": f"Bearer {token}"},
+    )
+    assert response.status_code == 401
+    assert "Token has expired" in response.json()["detail"]

+
+@pytest.mark.anyio
+async def test_create_post_missing_data(
+    async_client: AsyncClient, logged_in_token: str
+):
+    response = await async_client.post(
+        "/post",
+        json={},
+        headers={"Authorization": f"Bearer {logged_in_token}"},
+    )
     assert response.status_code == 422


@@ -50,11 +88,17 @@


 @pytest.mark.anyio
-async def test_create_comment(async_client: AsyncClient, created_post: dict):
+async def test_create_comment(
+    async_client: AsyncClient,
+    created_post: dict,
+    logged_in_token: str,
+):
     body = "Test Comment"

     response = await async_client.post(
-        "/comment", json={"body": body, "post_id": created_post["id"]}
+        "/comment",
+        json={"body": body, "post_id": created_post["id"]},
+        headers={"Authorization": f"Bearer {logged_in_token}"},
     )
     assert response.status_code == 201
     assert {