Skip to content

Repository files navigation

🛡️ Auth Service

Python Version FastAPI SQLAlchemy Pydantic Redis Alembic License

A production-ready Authentication microservice built with FastAPI. This service solves the repetitive task of building secure authentication for every project by providing a robust, scalable, and highly configurable starter kit.

🚀 Why this project?

Building a secure auth system is hard. This project follows industry standards and best practices:

  • Stateless & Scalable: Uses JWT with proper rotation and revocation.
  • Security First: Includes MFA (TOTP), Brute-force protection, and secure cookie handling.
  • Developer Friendly: Clean architecture, fully typed, and includes a comprehensive test suite.

✨ Features

  • Multi-Factor Authentication (MFA): Secure TOTP-based authentication via Authenticator apps.
  • Email/Password Auth: Secure hashing with bcrypt/argon2.
  • Google OAuth 2.0: Integrated social login support.
  • Session Management: Refresh token rotation with reuse detection and revocation.
  • Device Tracking: Monitor active sessions with location and device info.
  • Email Providers: Mailgun primary provider with SMTP fallback support.
  • Production Logging: JSON stdout logs designed for Docker json-file rotation.
  • Security: Rate limiting (Redis), account lockout, and hashed reset tokens.
  • Architecture: Async SQLAlchemy 2.0, Alembic migrations, and Dependency Injection.

🛠️ Tech Stack

  • Framework: FastAPI
  • Database: SQLAlchemy 2.x (Async), Alembic
  • Cache/Rate Limit: Redis (Async)
  • Security: PyJWT, bcrypt, pyotp
  • Email: Mailgun API or SMTP
  • Logging: Python logging, colorized local logs, JSON production logs
  • Validation: Pydantic 2.x
  • Testing: Pytest, Coverage

📋 API Reference (Main Endpoints)

Method Endpoint Description Auth
POST /api/v1/auth/register Create a new user account Public
POST /api/v1/auth/login Login user and get access token, or MFA token when MFA is enabled Public
POST /api/v1/auth/admin/login Login admin and get access token, or MFA token when MFA is enabled Public
GET /api/v1/auth/google/callback Complete Google OAuth login callback Public
POST /api/v1/auth/refresh Rotate refresh token and issue a new access token Refresh Cookie
POST /api/v1/auth/logout Revoke current session Refresh Cookie
POST /api/v1/auth/logout-all Revoke all active sessions Private
GET /api/v1/auth/sessions Get active sessions for current user Private
DELETE /api/v1/auth/sessions/{session_id} Revoke a specific session Private
POST /api/v1/auth/forgot-password Request password reset token/email Public
POST /api/v1/auth/reset-password Reset password using reset token Public
POST /api/v1/auth/change-password Change current user's password Private
POST /api/v1/auth/email/verification/me Generate email verification token for current user Private
POST /api/v1/auth/verify-email Verify email using verification token Public
POST /api/v1/auth/mfa/setup Generate MFA secret & QR code Private
POST /api/v1/auth/mfa/enable Verify & enable MFA for account Private
POST /api/v1/auth/mfa/disable Disable MFA for current user Private
POST /api/v1/auth/mfa/verify-login Finalize login with OTP code MFA Token

🛡️ MFA Flow

This service implements a secure two-step MFA process:

  1. Setup: User calls /mfa/setup (authenticated) to receive a secret and a QR code URL.
  2. Activation: User scans the QR and provides the first code to /mfa/enable to activate.
  3. Login: If enabled, /login returns a status: "mfa_required" and a temporary mfa_token.
  4. Verification: User submits the OTP and mfa_token to /mfa/verify-login to receive final Access & Refresh tokens.

🐳 Docker Quick Start

Run the entire stack (API + Redis + DB) with a single command:

docker-compose up --build

🏗️ Project Structure

app/
  apis/              API route modules
  cache/             Redis client
  celery/            Background task modules
  configs/           Settings, database, initialization
  constants/         Enums and messages
  core/              JWT, cookies, OAuth, security, lifespan
  dependencies/      FastAPI dependencies
  middleware/        Rate limiter
  models/            SQLAlchemy models
  repository/        Database repositories
  schemas/           Pydantic schemas
  services/          Business logic
migrations/          Alembic migration environment
requirements/        pip requirements files
scripts/             Helper scripts
tests/               Unit, integration, and e2e tests

Requirements

  • Python 3.12+
  • Redis
  • SQLite, MySQL, or another SQLAlchemy-supported database
  • uv or pip

Environment Setup

Create a .env file from the example:

cp .env.example .env

Required secrets:

Generate JWT Secret Using: openssl rand -hex 64

JWT_ACCESS_SECRET=change-me
JWT_REFRESH_SECRET=change-me

Google OAuth

GOOGLE_CLIENT_ID=change-me
GOOGLE_CLIENT_SECRET=change-me
GOOGLE_REDIRECT_URI=http://localhost:8000/api/v1/auth/google/callback

Mailgun

EMAIL_PROVIDER=mailgun
MAILGUN_API_URL=https://api.mailgun.net/v3
MAILGUN_API_KEY=change-me
MAILGUN_DOMAIN=change-me
MAILGUN_FROM_EMAIL=no-reply@example.com

SMTP fallback

EMAIL_PROVIDER=smtp
SMTP_TLS=true
SMTP_SSL=false
SMTP_PORT=587
SMTP_HOST=smtp.example.com
SMTP_USERNAME=change-me
SMTP_PASSWORD=change-me
SMTP_FROM_EMAIL=no-reply@example.com
SMTP_FROM_NAME=Auth Service

Common local values:

ENVIRONMENT=local
DEBUG=true
LOG_LEVEL=DEBUG
LOG_QUEUE_SIZE=5000
DATABASE_URL=sqlite+aiosqlite:///./test.db
SYNC_DATABASE_URL=sqlite:///./test.db
REDIS_URL=redis://localhost:6379/0
API_ROOT=/api
API_V1_PREFIX=/v1
COOKIE_SECURE=false
COOKIE_SAMESITE=lax

For MySQL, use an async database URL:

DATABASE_URL=mysql+aiomysql://user:password@localhost:3306/auth_service
SYNC_DATABASE_URL=mysql+pymysql://user:password@localhost:3306/auth_service

Installation

Using uv:

uv sync --extra dev

Using pip:

python -m venv .venv
source .venv/bin/activate
pip install -r requirements/dev.txt

On Windows PowerShell:

python -m venv .venv
.\.venv\Scripts\Activate.ps1
pip install -r requirements/dev.txt

Run Locally

Start Redis first, then run the app:

python run_dev.py

Or run Uvicorn directly:

uvicorn app.main:app --reload --host 0.0.0.0 --port 8000

API docs:

http://localhost:8000/docs
http://localhost:8000/redoc

Database Migrations

Create a migration:

alembic revision --autogenerate -m "create users tables"

Apply migrations:

alembic upgrade head

Check current migration:

alembic current

Helper script:

bash scripts/alembic_migration.sh create -m "migration message"
bash scripts/alembic_migration.sh upgrade
bash scripts/alembic_migration.sh current

Auth Flow

  1. User logs in with email/password or Google OAuth.
  2. Service returns a short-lived JWT access token.
  3. Service stores a hashed refresh token in user_sessions.
  4. Refresh token is sent as an HTTP-only cookie.
  5. Refreshing rotates the refresh token and revokes the previous session.
  6. Reuse or mismatch detection revokes all user sessions.

Logging Flow

The service uses app.configs.logging.get_logger(__name__) for application logs. setup_logging() runs during app startup from the FastAPI lifespan hook.

Logs are written to stdout only. This matches the production Docker setup where the container filesystem is read-only and Docker owns persistence/rotation through the json-file logging driver.

logger.info()
     |
     v
root logger
     |
     v
StreamHandler(stdout)
     |
     v
Docker json-file driver
     |
     v
host-managed container logs

Logging behavior:

  • Local/staging console logs use colored output.
  • Production logs are structured JSON on stdout.
  • The app does not write logs/app.log in Docker production.
  • Docker rotates logs at 10 MB.
  • Docker keeps 3 rotated log files.
  • read_only: true keeps the container filesystem immutable.
  • tmpfs: /tmp provides temporary writable space for runtime temp files.
  • Noisy libraries like uvicorn, sqlalchemy, httpx, and httpcore are suppressed.

Docker compose logging config:

logging:
  driver: "json-file"
  options:
    max-size: "10m"
    max-file: "3"
read_only: true
tmpfs:
  - /tmp

Why stdout-only:

app writes stdout
     |
     v
Docker captures logs
     |
     v
Docker rotates log files

Do not add app-level file logging in production unless you also mount a writable volume for logs.

Example usage:

from app.configs.logging import get_logger

logger = get_logger(__name__)
logger.info("User logged in", extra={"user_id": user_id})

Email Flow

The email service uses a provider interface so the app can switch between Mailgun and SMTP from environment settings.

EmailService
     |
     v
get_email_provider()
     |
     +----------------+
     |                |
     v                v
MailgunProvider   SMTPProvider
     |                |
     v                v
Mailgun API      SMTP server

Provider selection:

EMAIL_PROVIDER=mailgun

or:

EMAIL_PROVIDER=smtp

Usage:

from app.services.email import email_service

await email_service.send_email(
    recipients=["user@example.com"],
    subject="Verify your email",
    html_body="<p>Hello</p>",
)

Background sending:

await email_service.send_email_background(
    recipients=["user@example.com"],
    subject="Verify your email",
    html_body="<p>Hello</p>",
    background_tasks=background_tasks,
)

Errors are logged with logger.exception(). Background emails use raise_on_error=False so failed email delivery does not crash the request.

Lifespan Responsibilities

FastAPI lifespan is used for app-level startup and shutdown work. It should manage shared infrastructure resources, not per-request objects.

Current startup flow:

app startup
     |
     v
setup_logging()
     |
     v
init_redis()
     |
     v
RateLimiter(app.state.redis)

Current shutdown flow:

app shutdown
     |
     v
close_redis(app.state.redis)

Good things to put in lifespan:

  • Logging setup
  • Redis connection setup and close
  • Rate limiter initialization
  • Database engine health check
  • Background scheduler start/stop
  • Cache warmup
  • Metrics/tracing setup

Do not put these in lifespan:

  • Per-request database sessions
  • Current user loading
  • Auth checks
  • Request-specific service objects
  • Request-specific transactions

Use FastAPI dependencies for per-request work:

async def get_async_db() -> AsyncGenerator[AsyncSession, None]:
    async with AsyncSessionLocal() as session:
        yield session
db_deps = Annotated[AsyncSession, Depends(get_async_db)]

Rule:

lifespan = shared app resources
dependencies = per-request resources

Security Notes

  • Access tokens are short-lived.
  • Refresh tokens are stored only as hashes.
  • Refresh tokens are rotated on every refresh.
  • Session reuse detection revokes all sessions for the user.
  • Failed login attempts can temporarily lock an account.
  • Password reset tokens are hashed before storage.
  • Refresh cookies are HTTP-only by default.
  • Set COOKIE_SECURE=true in production.

Models

Main tables:

  • users
  • user_sessions
  • password_reset_tokens

User fields include:

  • name, email, phone, profile image
  • hashed password
  • Google ID
  • role
  • mfa enable, mfa secret
  • email verification status
  • active/superuser flags
  • login and lockout metadata
  • soft-delete metadata

Session fields include:

  • user ID
  • refresh token JTI
  • refresh token hash
  • user agent and IP address
  • expiry
  • revocation metadata
  • replacement session ID

Code Quality

Format:

ruff format .

Lint:

ruff check . --fix

Type-check:

mypy app

Helper script:

bash scripts/code_quality.sh all

Tests

Run tests:

pytest

Run with coverage:

pytest --cov=app

Coverage is configured to require 80%.

Production Notes

  • Use strong unique JWT secrets.
  • Use HTTPS.
  • Set COOKIE_SECURE=true.
  • Restrict ALLOWED_ORIGINS.
  • Use a production database.
  • Run migrations before deployment.
  • Run behind a process manager such as Gunicorn with Uvicorn workers.
  • Configure Redis persistence/availability based on deployment needs.

Production dependencies are listed in:

requirements/prod.txt

License

MIT License. See LICENSE.

About

Production-ready async auth microservice — JWT rotation, refresh tokens, Google OAuth 2.0, MFA (TOTP), Redis rate limiting, RBAC | FastAPI · SQLAlchemy 2.0 · MySQL · Redis

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Contributors

Languages