Project: Todo API With Authentication
Introduction
This project adds users, JWT login, and personal todo lists—each user sees only their tasks. You practice 401 vs 403, unified error JSON, and protected APIRouter routes. It is the full-stack API capstone before deployment and optional frontend integration.
Prerequisites
- Authentication and JWT
- Permissions and Authorization
- Project: REST CRUD API
- Middleware and CORS for SPA testing (optional)
Project Goals
POST /api/v1/auth/registerandPOST /api/v1/auth/loginGET/POST /api/v1/todos/— list and create (JWT required)PATCH/DELETE /api/v1/todos/{id}— owner only- Error body:
{"detail": "..."}or team envelope - Optional CORS for
http://localhost:5173
Step 1: Models
User: id, username, hashed_password
Todo: id, title, done, owner_id → ForeignKey("users.id")
Alembic:
alembic revision --autogenerate -m "Users and todos"
alembic upgrade headStep 2: Auth Routes
Implement from authentication chapter:
- Register with
UserCreateschema - Login with
OAuth2PasswordRequestForm GET /api/v1/auth/mewithDepends(get_current_user)
Step 3: Todo Routes
Ownership check helper:
def get_owned_todo(todo_id: int, db: Session, user: User) -> Todo:
todo = db.get(Todo, todo_id)
if todo is None:
raise HTTPException(status_code=404, detail="Todo not found")
if todo.owner_id != user.id:
raise HTTPException(status_code=403, detail="Not allowed")
return todoStep 4: Unified Errors (Optional)
@app.exception_handler(HTTPException)
async def http_exception_handler(request: Request, exc: HTTPException):
return JSONResponse(
status_code=exc.status_code,
content={"code": exc.status_code, "message": exc.detail, "data": None},
)Step 5: Test Flow With curl
Step 6: pytest
def test_todos_require_auth(client):
assert client.get("/api/v1/todos/").status_code == 401
def test_user_isolation(client):
# register user A and B, ensure A cannot delete B's todo
...Use login helper fixture from testing chapter.
Step 7: CORS for Frontend (Optional)
app.add_middleware(
CORSMiddleware,
allow_origins=["http://localhost:5173"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["Authorization", "Content-Type"],
)Frontend fetch with Authorization: Bearer ${token}.
Compare Flask Todo project—server-rendered vs API-only.
Verification Checklist
- Register → login → create todo → list shows one item
- Missing token returns 401
- Access other user's todo returns 403 or 404 (your policy)
-
pytestcovers auth and CRUD - Password never in JSON responses
FAQ
Login 422 with JSON body?
OAuth2 login expects form fields—use -d username=...&password=....
Refresh tokens?
Optional stretch—short access token sufficient for learning.
Admin sees all todos?
Add role == admin bypass in get_owned_todo.
OpenAPI Authorize?
Click Authorize in /docs, use login form or paste Bearer token.
Vue/React starter?
Any SPA calling /api/v1 with CORS—backend focus here.
Next project?
Inference API or deploy.