Module 6: Complete Auth System Project — Introduction
Capsule 07: API Documentation
Overview
Your auth system works and it's tested. But if another developer can't use it in 30 minutes, it isn't production-ready. Documentation is what separates "code that works" from "code you can hand over."
In this capsule you'll create: a professional README for developers consuming your API, OpenAPI tags and descriptions that show up in /docs, code examples for common flows, and the final SECURITY.md.
The three kinds of documentation
| Kind | Audience | Format |
|---|---|---|
| README | Developers who will use your API | Markdown |
| OpenAPI/Swagger | Quick reference, exploration | Auto-generated by FastAPI |
| SECURITY.md | Security teams, auditors | Markdown |
A professional README
# Auth System API
Production-grade auth system with FastAPI: register, login, JWT, RBAC, API keys.
## Stack
- Python 3.12+
- FastAPI
- PostgreSQL 16
- Redis 7
- argon2id (pwdlib) for password hashing
- PyJWT for JWT
- OAuth 2.0 password flow
## Quick Start
```bash
# 1. Clone
git clone https://github.com/you/auth-system.git
cd auth-system
# 2. Install
pip install -r requirements.txt
# 3. Setup .env
cp .env.example .env
# Edit .env with your values
# 4. Generate JWT_SECRET_KEY
python -c "import secrets; print(secrets.token_urlsafe(32))"
# Copy it into .env
# 5. Start Postgres and Redis
docker-compose up -d
# 6. Migrations
alembic upgrade head
# 7. Seed RBAC
python -m app.scripts.seed_rbac
# 8. Run
uvicorn app.main:app --reload
Visit /docs for the interactive Swagger UI.
Authentication Flow
1. Register
curl -X POST http://localhost:8000/api/v1/auth/register \
-H "Content-Type: application/json" \
-d '{"email":"alice@example.com","password":"purple horse jumps moon"}'
Response:
{
"id": 1,
"email": "alice@example.com",
"created_at": "2026-04-25T10:30:00Z"
}
2. Login
OAuth 2.0 password flow — form-data, NOT JSON:
curl -X POST http://localhost:8000/api/v1/auth/login \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "username=alice@example.com&password=purple horse jumps moon"
Response:
{
"access_token": "eyJhbGciOi...",
"refresh_token": "eyJhbGciOi...",
"token_type": "bearer"
}
3. Use access token
curl http://localhost:8000/api/v1/users/me \
-H "Authorization: Bearer eyJhbGciOi..."
4. Refresh
When the access token expires (15 min):
curl -X POST http://localhost:8000/api/v1/auth/refresh \
-H "Content-Type: application/json" \
-d '{"refresh_token":"eyJhbGciOi..."}'
Returns new access + refresh tokens. The old refresh becomes invalid (rotation).
5. Logout
curl -X POST http://localhost:8000/api/v1/auth/logout \
-H "Authorization: Bearer eyJhbGciOi..."
Token revoked instantly.
Roles & Permissions
Three roles with hierarchy:
- user — read posts, comment
- editor — user permissions + create/edit/delete OWN posts
- admin — editor permissions + manage users, edit/delete ANY post
[See ROLES.md for full matrix]
API Keys (for service-to-service)
# Create
curl -X POST http://localhost:8000/api/v1/api-keys \
-H "Authorization: Bearer <user-token>" \
-H "Content-Type: application/json" \
-d '{"name":"CI/CD"}'
# Use
curl http://localhost:8000/api/v1/integrations/posts \
-H "X-API-Key: sk_live_..."
API key plaintext shown ONCE. Save it securely.
Error Codes
| Status | Meaning |
|---|---|
| 200/201/204 | Success |
| 400 | Business rule violation |
| 401 | Authentication issue (no token, invalid, expired) |
| 403 | Authorization issue (no permission) |
| 404 | Resource not found |
| 409 | Conflict (e.g., email exists) |
| 422 | Validation error |
| 429 | Rate limit exceeded |
| 500 | Server error (bug — please report) |
Rate Limits
| Endpoint | Limit |
|---|---|
| POST /auth/register | 3/min per IP |
| POST /auth/login | 5/min per IP |
| POST /auth/refresh | 20/min per IP |
When rate limited: response includes Retry-After: <seconds> header.
Security
See SECURITY.md for security posture details and reporting vulnerabilities.
Development
# Run tests
pytest
# Coverage
pytest --cov=app
# Format
ruff format app/
# Lint
ruff check app/
# Type check
mypy app/
Production Deployment
See DEPLOYMENT.md.
License
MIT
---
## OpenAPI tags and descriptions
FastAPI auto-generates the documentation, but you can improve it:
```python
# app/main.py
app = FastAPI(
title="Auth System API",
version="1.0.0",
description="""
Production-grade authentication system.
## Features
- 🔐 Register, login, refresh, logout
- 🎭 RBAC with 3 roles (user, editor, admin)
- 🔑 API keys for service-to-service
- 🛡️ Argon2id password hashing
- 📝 OAuth 2.0 password flow + JWT
""",
contact={
"name": "Support",
"email": "support@myapp.com",
},
license_info={
"name": "MIT",
},
)
# Tags with descriptions
tags_metadata = [
{
"name": "auth",
"description": "Authentication endpoints: register, login, refresh, logout.",
},
{
"name": "users",
"description": "User-related endpoints (e.g., /me).",
},
{
"name": "posts",
"description": "Blog posts CRUD. Public reads, authenticated writes.",
},
{
"name": "admin",
"description": "Admin-only endpoints. Requires `admin` role.",
},
{
"name": "api-keys",
"description": "Manage API keys for service-to-service authentication.",
},
{
"name": "integrations",
"description": "Service-to-service endpoints. Authenticated via API key (X-API-Key header).",
},
]
app.openapi_tags = tags_metadata
Endpoint descriptions
@router.post(
"/register",
response_model=UserPublic,
status_code=status.HTTP_201_CREATED,
summary="Register a new user",
description="""
Creates a new user account.
**Validation:**
- Email must be valid format
- Password: 8-128 chars, not in common passwords list
**On success:** returns user profile (without password hash).
**On error:**
- 409 if email already registered
- 422 if validation fails
- 429 if rate limited (3/min per IP)
""",
responses={
201: {"description": "User created successfully"},
409: {"description": "Email already registered"},
422: {"description": "Validation error"},
429: {"description": "Rate limit exceeded"},
},
)
@limiter.limit("3/minute")
async def register(...):
...
Examples in OpenAPI
from pydantic import BaseModel, ConfigDict
class UserRegister(BaseModel):
model_config = ConfigDict(
json_schema_extra={
"example": {
"email": "alice@example.com",
"password": "purple horse jumps moon",
}
}
)
email: EmailStr
password: str = Field(..., min_length=8, max_length=128)
/docs will show the example automatically, easy to run.
Multiple examples
class LoginRequest(BaseModel):
model_config = ConfigDict(
json_schema_extra={
"examples": [
{
"summary": "Standard login",
"value": {
"username": "alice@example.com",
"password": "secure_password",
},
},
{
"summary": "Login with scope",
"value": {
"username": "alice@example.com",
"password": "secure_password",
"scope": "read:posts write:posts",
},
},
]
}
)
The final SECURITY.md
# Security Posture
## Reporting Vulnerabilities
**DO NOT** report security issues via GitHub issues.
Send to: security@myapp.com
We'll respond within 48 hours and coordinate disclosure.
## Stack
- **Password hashing:** argon2id via pwdlib (NIST SP 800-63B compliant)
- **JWT:** PyJWT with HS256, secret rotated every 6 months
- **OAuth 2.0:** Password flow with refresh tokens
- **Rate limiting:** slowapi with Redis backend
- **Token revocation:** Redis blacklist with auto-TTL
- **Security headers:** HSTS, CSP, X-Frame-Options, X-Content-Type-Options, Referrer-Policy
## Authentication
### Password Storage
- Argon2id with default parameters (memory=64MB, time=3, parallelism=4)
- NIST 2024 compliant: no composition rules, length 8+, blacklist of common passwords
- 256-bit salt generated automatically per password
### Token Management
- Access tokens: 15 minute expiration
- Refresh tokens: 7 day expiration with rotation
- Each token has unique JTI (JWT ID)
- Logout invalidates token via Redis blacklist
- Token versioning for "logout all devices" (admin action)
### Rate Limiting
- POST /auth/register: 3/min per IP
- POST /auth/login: 5/min per IP
- POST /auth/refresh: 20/min per IP
## Authorization
Role-based access control (RBAC) with hierarchy:
- `user` (default) — read access
- `editor` — content management
- `admin` — full access
13 granular permissions, see ROLES.md.
## OWASP API Top 10 (2023) Coverage
| Risk | Status |
|---|---|
| API1: BOLA | ✅ Mitigated |
| API2: Broken Auth | ✅ Mitigated |
| API3: Object Property Auth | ✅ Mitigated |
| API4: Resource Consumption | ✅ Mitigated (pagination, rate limit) |
| API5: Function Auth | ✅ Mitigated (RBAC) |
| API6: Business Flows | N/A |
| API7: SSRF | N/A |
| API8: Misconfiguration | ✅ Mitigated |
| API9: Inventory | ✅ Mitigated |
| API10: Unsafe API Consumption | N/A |
## Compliance
- GDPR: data minimization, right to deletion (admin endpoint)
- SOC 2 Type 1: in progress
- Pen tested: annually by [Vendor]
## Audit Log
Security-relevant events are logged:
- Failed login attempts (with IP, but NOT password)
- Successful login (user_id, IP, timestamp)
- Logout
- Role changes
- API key create/delete
- Permission denials
Logs retained for 1 year.
## Incident Response
If you suspect a security incident:
1. Email security@myapp.com immediately
2. Include: timestamp, affected endpoints, suspicious behavior
3. We'll respond within 4 hours during business hours
ROLES.md (RBAC documentation)
# Roles & Permissions
## Roles
### user (default)
Permissions: posts:read, comments:create, comments:delete:own
Can:
- Read all posts
- Create comments
- Delete their own comments
### editor
Inherits user, plus:
- posts:create
- posts:edit:own
- posts:delete:own
Can:
- All `user` actions
- Create posts
- Edit their own posts
- Delete their own posts
### admin
Inherits editor, plus:
- posts:edit:any
- posts:delete:any
- users:read
- users:manage
- roles:assign
- roles:manage
Can:
- All `editor` actions
- Edit/delete any post
- List/delete users
- Assign/remove roles
## Permission Naming
Format: `resource:action[:scope]`
- `resource` — what entity (posts, users, etc.)
- `action` — what to do (read, create, edit, delete)
- `scope` (optional) — `own` or `any`
## Endpoint × Role Matrix
[Full table from Module 6, capsule 4]
## Adding a New Role
1. Edit `app/scripts/seed_rbac.py`
2. Add to `ROLE_PERMISSIONS_DIRECT`
3. Optionally add to `ROLE_HIERARCHY`
4. Run: `python -m app.scripts.seed_rbac`
5. No code changes needed in endpoints
DEPLOYMENT.md
# Deployment Guide
## Production Checklist
### Pre-deployment
- [ ] All tests passing
- [ ] Coverage > 90%
- [ ] No secrets in code
- [ ] DEBUG=False
- [ ] /docs disabled (or behind auth)
- [ ] CORS configured (no `*`)
- [ ] HTTPS enabled
- [ ] PostgreSQL replica set up
- [ ] Redis with persistence enabled
- [ ] Logging configured (Sentry/CloudWatch/etc.)
- [ ] Monitoring set up (response times, error rates)
- [ ] Backups automated
### Environment Variables
```bash
# Required
DATABASE_URL=postgresql+asyncpg://...
JWT_SECRET_KEY=<openssl rand -base64 32>
REDIS_URL=redis://...
# Recommended
ENVIRONMENT=production
CORS_ORIGINS=["https://myapp.com"]
ACCESS_TOKEN_EXPIRE_MINUTES=15
REFRESH_TOKEN_EXPIRE_DAYS=7
Infrastructure
Minimum:
- 1 application instance (2 vCPU, 4 GB RAM)
- 1 PostgreSQL instance (with daily backups)
- 1 Redis instance
Recommended:
- 2+ application instances behind load balancer
- PostgreSQL primary + replica
- Redis with replication
CI/CD
See .github/workflows/test.yml and .github/workflows/deploy.yml.
Common Deployments
Render
# render.yaml
services:
- type: web
name: auth-api
env: python
buildCommand: pip install -r requirements.txt && alembic upgrade head
startCommand: uvicorn app.main:app --host 0.0.0.0 --port $PORT
Docker
[Dockerfile + docker-compose example]
AWS
[ECS / Lambda / EC2 instructions]
---
## Expected outputs
After this capsule you have:
- `README.md` — getting started + API usage
- `SECURITY.md` — security posture
- `ROLES.md` — RBAC reference
- `DEPLOYMENT.md` — production guide
- An improved `/docs` with descriptions and examples
Any developer can onboard in under an hour.
---
## Troubleshooting
### Problem 1: The README is too long
Split it into separate sections (DEPLOYMENT.md, ROLES.md). The README should only have the quick start + links.
### Problem 2: /docs doesn't show examples
Check `model_config = ConfigDict(json_schema_extra=...)` in your Pydantic models.
### Problem 3: SECURITY.md has sensitive info
Be careful with details about:
- Internal structure (paths to sensitive files)
- Specific software versions (adversaries can look up CVEs)
- IP addresses / server names
Keep it general — save the specifics for incidents.
---
## Exercises
### Exercise 1: Write the README
Implement the README for your system. It should let a new developer get from setup to login in under 30 min.
<details>
<summary>See solution</summary>
Use the template from the capsule. Customize it for your specific project.
</details>
### Exercise 2: Improve /docs
Add tags metadata, descriptions, and examples to your main endpoints.
<details>
<summary>See solution</summary>
The code is in the capsule. Apply it to:
- /auth/register
- /auth/login
- /auth/refresh
- /posts (POST, PATCH, DELETE)
- /admin/users/{id}/roles
</details>
### Exercise 3: SECURITY.md
Implement SECURITY.md following the template.
<details>
<summary>See solution</summary>
The template is in the capsule. Adapt:
- A real contact email
- The specific stack of your deployment
- Whatever compliance applies to your case
</details>
### Exercise 4: A test that the README still works
Implement a test that runs the README's commands and verifies they work. Useful to keep the README from going stale.
<details>
<summary>See solution</summary>
```bash
#!/bin/bash
# scripts/test_readme.sh
set -e
# Assuming a clean environment
pip install -r requirements.txt
alembic upgrade head
python -m app.scripts.seed_rbac
# Try the README's register command
RESPONSE=$(curl -s -X POST http://localhost:8000/api/v1/auth/register \
-H "Content-Type: application/json" \
-d '{"email":"test@readme.com","password":"purple horse jumps moon"}')
echo "$RESPONSE" | jq -e '.id' > /dev/null || (echo "Register failed" && exit 1)
# Login
TOKENS=$(curl -s -X POST http://localhost:8000/api/v1/auth/login \
-d "username=test@readme.com&password=purple horse jumps moon")
ACCESS=$(echo "$TOKENS" | jq -r .access_token)
# /me
curl -s http://localhost:8000/api/v1/users/me \
-H "Authorization: Bearer $ACCESS" | jq -e '.email' > /dev/null
echo "✓ All README commands work"
Run it in CI before every deploy.
Exercise 5: Generate an exportable OpenAPI spec
Export your OpenAPI spec to an openapi.json file.
See solution
# Manual
curl http://localhost:8000/openapi.json > openapi.json
# Or with a script
python -c "
from app.main import app
import json
print(json.dumps(app.openapi(), indent=2))
" > openapi.json
Useful for:
- Generating client SDKs (with tools like openapi-generator)
- Offline documentation
- Versioning the API
Summary
- A professional README = onboarding in under 30 min for new developers.
- Quick Start is the critical section — it has to work end-to-end.
- OpenAPI tags + descriptions + examples improve /docs significantly.
- SECURITY.md documents posture, not detailed implementation.
- ROLES.md is the RBAC reference for auditors and devs.
- DEPLOYMENT.md is the production guide.
- Documentation is code — versioned, tested, maintained.
Additional resources
- README best practices (GitHub) — Examples
- OpenAPI Specification — The official spec
- FastAPI — Path Operation Configuration — Official docs
- Diátaxis — A framework for technical documentation
- API Documentation Best Practices — Guidelines
Next step
Capsule 08: Final project + Production checklist. Complete verification, production deployment notes, delivery of the system.