A production-pattern FastAPI application demonstrating:
- JWT authentication with access + refresh tokens
- Refresh token rotation
- Role-based access control (RBAC)
- SQLite database with SQLAlchemy ORM
- Reusable pagination across any endpoint
- Comprehensive pytest test suite
- Redis to store revoked token set
jwt_api/
├── main.py # App entry point, CORS, router registration
├── pyproject.toml # Project metadata + dependencies
├── .env.example # Environment variable template
├── .gitignore
│
├── core/
│ ├── config.py # Centralised settings (env-var ready)
│ ├── security.py # JWT create/decode, bcrypt password hashing
│ ├── dependencies.py # get_current_user, require_admin (Depends)
│ └── pagination.py # Reusable paginate() helper
│
├── db/
│ ├── database.py # SQLAlchemy engine, session, get_db()
│ ├── orm_models.py # UserORM and ProductORM table definitions
│ └── seed.py # Seeds sample users and products on startup
│
├── models/
│ └── user.py # Pydantic schemas for auth endpoints
│
├── routers/
│ ├── auth.py # /register /login /refresh /logout
│ ├── users.py # /me (any user), / and /{username} (admin only)
│ └── products.py # Paginated products with category filter
│
└── tests/
└── test_auth.py # 19 tests — auth flows + pagination
git clone <your-repo-url>
cd jwt_api
pip install -e ".[dev]"cp .env.example .env
# Edit .env and set a strong SECRET_KEY:
# openssl rand -hex 32uvicorn main:app --reloadThe database is created and seeded automatically on first run.
http://127.0.0.1:8000/docs
Click Authorize → log in as alice / secret123 (admin) or bob / pass456 (user).
| Method | Endpoint | Access | Description |
|---|---|---|---|
| POST | /api/v1/auth/register |
Public | Create account |
| POST | /api/v1/auth/login |
Public | Get access + refresh tokens |
| POST | /api/v1/auth/refresh |
Public | Rotate refresh token |
| POST | /api/v1/auth/logout |
Public | Revoke refresh token |
| Method | Endpoint | Access | Description |
|---|---|---|---|
| GET | /api/v1/users/me |
Any user | Own profile |
| GET | /api/v1/users/ |
Admin only | Paginated user list |
| GET | /api/v1/users/{username} |
Admin only | Look up user |
| Method | Endpoint | Access | Description |
|---|---|---|---|
| GET | /api/v1/products/ |
Any user | Paginated product list |
| GET | /api/v1/products/?category=Widgets |
Any user | Filter by category |
| GET | /api/v1/products/{id} |
Any user | Single product |
All list endpoints return a consistent paginated response:
{
"data": [...],
"page": 1,
"page_size": 5,
"total": 25,
"total_pages": 5,
"has_next": true,
"has_previous": false
}Query params: ?page=1&page_size=5 (page_size max: 100)
pytest tests/ -v19 tests covering:
- Registration, login, wrong password, user enumeration prevention
- Protected endpoints — authenticated and unauthenticated
- RBAC — admin vs regular user
- Refresh token rotation and reuse prevention
- Logout and token revocation
- Pagination — page navigation, last page, category filter, size cap
| Username | Password | Role |
|---|---|---|
| alice | secret123 | admin |
| bob | pass456 | user |
| carol–leo | pass456 | user |
| Pattern | Implementation |
|---|---|
| Short-lived access tokens | 30 min expiry |
| Refresh token rotation | Old token revoked on every refresh |
| Token type enforcement | Refresh tokens rejected on protected endpoints |
| User enumeration prevention | Same error for wrong user and wrong password |
| RBAC | require_admin dependency chains on get_current_user |
| Password hashing | bcrypt via direct library |
| Secret management | Environment variables via pydantic-settings |
| Response stripping | Pydantic response_model never leaks hashed_password |
- Rate limiting —
slowapion/loginand/register - PostgreSQL — replace SQLite for production
- Alembic — database migrations
- Structured logging — JSON logs per request
- Docker + docker-compose
- TLS termination — Nginx or AWS ALB (outside this codebase)