Production-oriented JWT authentication service built with FastAPI, PostgreSQL, and Redis.
Implements secure JWT authentication with access/refresh token rotation, Redis-backed revocation, Role-Based Access Control (RBAC), and a fully async layered architecture designed for scalability and maintainability.
- FastAPI app with versioned API prefix:
/api/v1 - JWT authentication:
- Access token in response body
- Refresh token in
HttpOnlycookie
- Role-Based Access Control (RBAC)
- User role embedded into JWT access tokens
- Role-based endpoint protection via FastAPI dependencies
- Refresh token rotation with one-time use semantics via Redis
GETDEL - Password hashing with Argon2 (Passlib)
- Async SQLAlchemy + asyncpg
- Alembic migration setup
- Centralized exception handling
- Basic rate limiting middleware (SlowAPI)
- CORS middleware configuration
- Automated CI/CD pipelines (GitHub Actions)
- Python
- FastAPI
- SQLAlchemy (async)
- PostgreSQL (asyncpg)
- Redis
- Alembic
- PyJWT
- Passlib (argon2)
- Pydantic Settings
- Ruff, Mypy, and Black (Linting & Formatting)
- GitHub Actions (CI/CD)
.
|- alembic/
| |- env.py
| |- versions/
|- app/
| |- main.py
| |- lifespan.py
| |- api/
| | |- dependencies/
| | |- middlewares/
| | |- v1/
| | | |- endpoints/
| |- auth/
| |- core/
| |- crud/
| |- db/
| |- exceptions/
| |- models/
| |- schemas/
| |- services/
| |- utils/
|- tests/
| |- fakes/
| |- unit/
|- alembic.ini
|- pyproject.toml
|- poetry.lock
|- pytest.ini
|- .env.template
|- .env.docker
|- README.md
- Register a user (
POST /api/v1/register) - Login (
POST /api/v1/login) - Receive:
access_tokenin JSON responserefresh_tokenin secureHttpOnlycookie
- Use access token for protected endpoints (Bearer auth)
- Access token carries authenticated user information including assigned role
- Protected endpoints can enforce role-based authorization
- Refresh access token (
POST /api/v1/refresh) using refresh cookie - Logout (
POST /api/v1/logout) revokes refresh token in Redis and clears cookie
Auth endpoints are under /api/v1. Health endpoints are top-level.
| Method | Path | Auth | Description |
|---|---|---|---|
| GET | /health/live |
No | Liveness probe |
| GET | /health/ready |
No | Readiness probe (checks Postgres + Redis) |
| POST | /api/v1/register |
No | Register new user |
| POST | /api/v1/login |
No | Login with username/password form |
| POST | /api/v1/refresh |
No | Rotate refresh token and issue new access token |
| POST | /api/v1/logout |
No | Revoke current refresh token and clear cookie |
| GET | /api/v1/about_me |
Bearer | Get current authenticated user |
- Python 3.12+
- Poetry 2.0+
- PostgreSQL
- Redis
If you do not have Poetry installed:
pipx install poetryRuntime dependencies:
poetry installDevelopment and testing tools:
poetry install --with devTip: you can run poetry shell once and then run commands without poetry run.
Create .env from template:
cp .env.template .envOn Windows PowerShell, use:
Copy-Item .env.template .envSet the required variables:
| Variable | Required | Notes |
|---|---|---|
DEBUG |
No | true or false |
CORS_ORIGINS |
No | JSON array of allowed origins |
DATABASE_URL |
Yes | Must start with postgresql+asyncpg:// or postgres+asyncpg:// |
REDIS_URL |
Yes | Must start with redis:// or rediss:// |
ACCESS_SECRET |
Yes | Min length: 32 |
REFRESH_SECRET |
Yes | Min length: 32 |
ACCESS_TOKEN_EXPIRE_M |
No | Default: 15 |
REFRESH_TOKEN_EXPIRE_M |
No | Default: 43200 |
COOKIE_SECURE |
No | Default: true |
COOKIE_SAMESITE |
No | One of lax, strict, none |
Important for local HTTP development:
- If you test without HTTPS, set
COOKIE_SECURE=false - Otherwise browser/client may not send refresh cookie, and
/refresh//logoutcan fail with401
poetry run alembic upgrade headpoetry run uvicorn app.main:app --reload --host 0.0.0.0 --port 8000Open:
- Swagger UI:
http://127.0.0.1:8000/docs - ReDoc:
http://127.0.0.1:8000/redoc
- Update
.env.dockerwith real secrets. - Start services:
docker compose up --build- Run migrations:
docker compose run --rm app alembic upgrade headMakefile shortcuts:
make up
make down
make migrate
make makemigrations m="describe change"
make logsFor production deployment (e.g., on a VPS), a dedicated compose file is provided:
docker compose -f docker-compose.prod.yml up -d --buildRun all tests:
poetry run pytestRun only unit tests:
poetry run pytest tests/unitFormat code with Black:
poetry run black .Lint code with Ruff:
poetry run ruff check .Check types with Mypy:
poetry run mypy appcurl -X POST "http://127.0.0.1:8000/api/v1/register" \
-H "Content-Type: application/json" \
-d '{"username":"john_doe","email":"john@example.com","password":"StrongPass123"}'curl -i -X POST "http://127.0.0.1:8000/api/v1/login" \
-H "Content-Type: application/x-www-form-urlencoded" \
-c cookies.txt \
-d "username=john_doe&password=StrongPass123"curl -i -X POST "http://127.0.0.1:8000/api/v1/refresh" \
-b cookies.txt \
-c cookies.txtcurl -X GET "http://127.0.0.1:8000/api/v1/about_me" \
-H "Authorization: Bearer <access_token>"curl -i -X POST "http://127.0.0.1:8000/api/v1/logout" \
-b cookies.txt \
-c cookies.txtApplication-specific errors use this shape:
{"detail": "..."}Common statuses:
201Created (register)200OK (login, refresh, logout, about_me)401Unauthorized (invalid credentials/token)404Not Found (user not found)409Conflict (user already exists)429Too Many Requests (rate limit exceeded)
Create migration:
poetry run alembic revision --autogenerate -m "describe change"Upgrade DB:
poetry run alembic upgrade headDowngrade one revision:
poetry run alembic downgrade -1- Test coverage is unit-focused; no end-to-end tests yet