Phase 3: API Endpoints & Authentication

RESTful Design & JWT Authentication

3 min read

In this phase we wire up the public face of TaskFlow: API endpoints that follow REST conventions, secured with JWT tokens and guarded by role-based access control.

RESTful URL Structure

A clean REST API maps resources to URLs and uses HTTP methods to express intent:

Method URL Pattern Purpose Status Code
POST /api/v1/projects Create a project 201 Created
GET /api/v1/projects List projects 200 OK
GET /api/v1/projects/{id} Get one project 200 OK
PUT /api/v1/projects/{id} Update a project 200 OK
DELETE /api/v1/projects/{id} Delete a project 204 No Content
POST /api/v1/projects/{id}/tasks Create a task in project 201 Created

Rules of thumb:

  • Use plural nouns for collections (/projects, not /project).
  • Nest child resources under parents (/projects/{id}/tasks).
  • Version your API (/api/v1/) so breaking changes never surprise clients.
  • Return the correct status code: 201 for creation, 204 for deletion, 404 when not found, 403 when forbidden, 422 for validation errors.

JWT Authentication Flow

TaskFlow uses JSON Web Tokens (JWT) for stateless authentication:

Register        Login             Access Protected Route
────────►  ────────────►  ────────────────────────────────►
POST         POST              GET /api/v1/projects
/auth/       /auth/login       Authorization: Bearer <token>
register     ─► JWT token      ─► Server decodes token
                                  ─► Identifies user
                                  ─► Returns data

A JWT has three parts separated by dots: header.payload.signature. The server signs the token with a secret key; on every request, it verifies the signature without hitting the database.

from datetime import datetime, timedelta, timezone
from jose import jwt, JWTError

SECRET_KEY = "your-secret-key"  # loaded from environment variable
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30

def create_access_token(data: dict) -> str:
    to_encode = data.copy()
    expire = datetime.now(timezone.utc) + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
    to_encode.update({"exp": expire})
    return jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)

Password Hashing with pwdlib + Argon2

FastAPI's current documentation recommends pwdlib with the Argon2 backend instead of the older passlib/bcrypt combination. Argon2 is the winner of the Password Hashing Competition and is resistant to GPU-based brute-force attacks:

from pwdlib import PasswordHash
from pwdlib.hashers.argon2 import Argon2Hasher

password_hash = PasswordHash((Argon2Hasher(),))

hashed = password_hash.hash("user-password")           # hash
is_valid = password_hash.verify("user-password", hashed) # verify

FastAPI Dependency Injection for Auth

FastAPI's Depends() system lets you inject the current user into any endpoint. The dependency reads the Authorization: Bearer <token> header, decodes the JWT, looks up the user in the database, and returns the user object (or raises 401 Unauthorized):

from fastapi import Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer

oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/v1/auth/login")

async def get_current_user(
    token: str = Depends(oauth2_scheme),
    db: AsyncSession = Depends(get_db),
) -> User:
    try:
        payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
        user_id: str = payload.get("sub")
        if user_id is None:
            raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED)
    except JWTError:
        raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED)

    user = await db.get(User, int(user_id))
    if user is None:
        raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED)
    return user

Any endpoint that needs authentication simply adds current_user: User = Depends(get_current_user) to its signature. FastAPI handles the rest.

Role-Based Access Control (RBAC)

In TaskFlow, every user has a role within a project (owner, admin, or member). Access checks happen through another dependency:

from enum import Enum

class ProjectRole(str, Enum):
    OWNER = "owner"
    ADMIN = "admin"
    MEMBER = "member"

def require_project_role(*allowed_roles: ProjectRole):
    async def checker(
        project_id: int,
        current_user: User = Depends(get_current_user),
        db: AsyncSession = Depends(get_db),
    ) -> ProjectMember:
        member = await get_project_member(db, project_id, current_user.id)
        if member is None or member.role not in allowed_roles:
            raise HTTPException(status_code=status.HTTP_403_FORBIDDEN)
        return member
    return checker

Usage in an endpoint:

@router.put("/projects/{project_id}")
async def update_project(
    project_id: int,
    data: ProjectUpdate,
    member: ProjectMember = Depends(
        require_project_role(ProjectRole.OWNER, ProjectRole.ADMIN)
    ),
    db: AsyncSession = Depends(get_db),
):
    # only owner or admin reaches this line
    ...

Pagination Pattern

For list endpoints, TaskFlow uses page/size pagination with a total count so the frontend can render page controls:

from pydantic import BaseModel
from typing import Generic, TypeVar, Sequence

T = TypeVar("T")

class PaginatedResponse(BaseModel, Generic[T]):
    items: Sequence[T]
    total: int
    page: int
    size: int
    pages: int

Query parameters ?page=1&size=20 drive the offset calculation: offset = (page - 1) * size. The response always includes total (the full count) and pages (the total number of pages).

Automatic OpenAPI Docs

FastAPI generates interactive API documentation from your code at no extra effort:

  • Swagger UI at /docs -- test endpoints directly in the browser.
  • ReDoc at /redoc -- a clean, readable reference.

Pydantic v2 models, response status codes, and dependency-injected auth all appear automatically in the generated docs. Clients can export the OpenAPI JSON from /openapi.json to generate SDKs in any language.


Next: Hands-on lab -- you will build all auth, project, and task endpoints for TaskFlow. :::

Quiz

Module 3 Quiz: API Endpoints & Authentication

Take Quiz