User authentication with FastAPI

Adding user registration and tests

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

New files

storeapi/routers/user.py
import logging

from fastapi import APIRouter, HTTPException, status
from storeapi.database import database, user_table
from storeapi.models.user import UserIn
from storeapi.security import get_user

logger = logging.getLogger(__name__)
router = APIRouter()


@router.post("/register", status_code=201)
async def register(user: UserIn):
    if await get_user(user.email):
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="A user with that email already exists",
        )
    # This is a VERY BAD idea! You should never store passwords in plain text!
    query = user_table.insert().values(email=user.email, password=user.password)

    logger.debug(query)

    await database.execute(query)
    return {"detail": "User created."}
storeapi/tests/test_security.py
import pytest
from storeapi import security


@pytest.mark.anyio
async def test_get_user(registered_user: dict):
    user = await security.get_user(registered_user["email"])
    assert user.email == registered_user["email"]


@pytest.mark.anyio
async def test_get_user_not_found():
    user = await security.get_user("test@example.com")
    assert user is None
storeapi/models/user.py
from pydantic import BaseModel


class User(BaseModel):
    id: int | None = None
    email: str


class UserIn(User):
    password: str

Modified files

storeapi/main.py
--- 
+++ 
@@ -7,6 +7,7 @@
 from storeapi.database import database
 from storeapi.logging_conf import configure_logging
 from storeapi.routers.post import router as post_router
+from storeapi.routers.user import router as user_router

 logger = logging.getLogger(__name__)

@@ -19,8 +20,7 @@

 app = FastAPI(lifespan=lifespan)
 app.add_middleware(CorrelationIdMiddleware)
-
-
+app.include_router(user_router)
 app.include_router(post_router)
storeapi/tests/conftest.py
--- 
+++ 
@@ -6,7 +6,7 @@
 from httpx import AsyncClient

 os.environ["ENV_STATE"] = "test"
-from storeapi.database import database  # noqa: E402
+from storeapi.database import database, user_table  # noqa: E402
 from storeapi.main import app  # noqa: E402


@@ -31,3 +31,13 @@
 async def async_client(client) -> AsyncGenerator:
     async with AsyncClient(app=app, base_url=client.base_url) as ac:
         yield ac
+
+
+@pytest.fixture()
+async def registered_user(async_client: AsyncClient):
+    user_details = {"email": "test@example.net", "password": "1234"}
+    await async_client.post("/register", json=user_details)
+    query = user_table.select().where(user_table.c.email == user_details["email"])
+    user = await database.fetch_one(query)
+    user_details["id"] = user.id
+    return user_details