Skip to content

Repository files navigation

Identyx

 ___    _            _              
|_ _|__| | ___ _ __ | |_ _   ___  __
 | |/ _` |/ _ \ '_ \| __| | | \ \/ /
 | | (_| |  __/ | | | |_| |_| |>  < 
|___\__,_|\___|_| |_|\__|\__, /_/\_\
                         |___/      

Authentication & Identity API — v1.0.0

CI License Python FastAPI

Identyx is a production-ready authentication and identity platform built with FastAPI and a microservices architecture. It provides email/password authentication, JWT token rotation, session management, email verification, password reset and transactional email alerts — designed for security, scalability and seamless integration across web and mobile applications.

Features

Feature Status
Email / Password auth ✅ Done
Email verification (HMAC one-time tokens) ✅ Done
JWT access + refresh tokens with rotation ✅ Done
Session management & revocation ✅ Done
Multi-device session limit (oldest revoked) ✅ Done
User profile management & avatars ✅ Done
Transactional emails via Kafka/Redpanda ✅ Done
New-login alerts (device + IP geolocation) ✅ Done
Brute-force protection & suspicious-login alerts ✅ Done
GDPR account deletion (email-confirmed) ✅ Done
Email change with re-verification ✅ Done
Password confirmation on sensitive actions ✅ Done
Prometheus metrics + Grafana dashboards ✅ Done
OpenTelemetry distributed traces (Tempo) ✅ Done
CI: lint, unit tests, E2E suite ✅ Done
OAuth 2.0 providers (Google, GitHub, …) 🔜 Planned
Passkeys (WebAuthn) 🔜 Planned
Multi-factor authentication (MFA / TOTP) 🔜 Planned

Architecture

1. System overview

Identyx follows an API Gateway + microservices pattern. A single gateway is the only externally reachable component; it authenticates every request and proxies it to one of 5 dedicated services, each owning its own data store.

                                ┌─────────────────────┐
                                │       CLIENTS       │
                                │  Web · Mobile · CLI │
                                └──────────┬──────────┘
                                           ▼
┌─────────────────────────────────────────────────────────────────────────────────────┐
│                                  GATEWAY  ·  :8100                                  │
│ SecurityHeaders → RateLimit → Metrics → CORS → Logging → Errors → JWT Auth          │
│ · Redis sliding-window rate limiting                                                │
│    (login 10/min · register 5/min · refresh 20/min · global 100/min)                │
│ · JWT validation via token-service + X-User-Id injection                            │
└────┬────────────────┬────────────────┬────────────────┬────────────────┬────────────┘
     │                │                │                │                │
     ▼                ▼                ▼                ▼                ▼
┌─────────┐      ┌─────────┐      ┌─────────┐      ┌─────────┐      ┌─────────┐
│   AUTH  │      │   USER  │      │  TOKEN  │      │ SESSION │      │  EMAIL  │
│  :8002  │      │  :8001  │      │  :8003  │      │  :8004  │      │  :8005  │
└────┬────┘      └────┬────┘      └────┬────┘      └────┬────┘      └────┬────┘
     │                │                │                │                │
     ▼                ▼                ▼                ▼                │
┌─────────┐      ┌─────────┐      ┌─────────┐      ┌─────────┐           │
│   auth  │      │  users  │      │  DB 0   │      │ session │           │
└─────────┘      └─────────┘      └─────────┘      └─────────┘           │
                                                                         │
                                                                         │
             ┌───────────────────────────────────────────────────────────┴────────────┐
             │ Event bus (Redpanda / Kafka)                                           │
             │ user.registered · auth.login · auth.new_login · auth.suspicious        │
             │ user.deletion_requested · user.email_change_requested                  │
             └───────────────────────────────────────────────────────────┬────────────┘
                                                                         ▼
                                                                ┌─────────────────┐
                                                                │   SMTP (Brevo)  │
                                                                └─────────────────┘

Message flow in a nutshell:

  1. A request enters the gateway on :8100.
  2. The gateway applies the middleware chain (security headers, rate limit, metrics, CORS, logging, error handling, JWT auth).
  3. For protected routes, the gateway validates the JWT through the token-service (POST /tokens/verify) and injects X-User-Id.
  4. The request is proxied to the owning service (/v1/auth/* → auth-service, /v1/users/* → user-service, /v1/sessions/* → session-service).
  5. Services collaborate synchronously (HTTP + X-Internal-Key) for request/response operations and asynchronously (Kafka events) for notifications.

2. Component responsibilities

Component Port Responsibility
gateway 8100 Single entry point: rate limiting, JWT validation, routing, CORS, metrics, security headers, /health + /metrics
auth-service 8002 Register, login, logout, token refresh, email verification, password reset, account deletion & email change (email-confirmed); Argon2id hashing, brute-force protection, purpose-bound HMAC tokens, event publishing
user-service 8001 User profiles, avatar upload (JPEG/PNG/WebP ≤ 5 MB), one-time token storage (verification, reset, deletion, email-change); internal endpoints for auth-service
token-service 8003 JWT generation, verification (incl. iss/aud) and blacklisting (Redis DB 0). Internal only
session-service 8004 Session lifecycle, single-use refresh-token rotation, multi-device limit (oldest session revoked)
email-service 8005 Consumes Kafka events and sends transactional emails (verification, security alert, new-login alert with IP geolocation, deletion & email-change confirmations)
redpanda 9092 Kafka-compatible message broker for async events
redis 6379 Token blacklist (DB 0), service state (DB 1), rate limiting & brute-force counters (DB 2)
postgres-* 5432 One isolated PostgreSQL instance per service (auth, users, sessions)
prometheus 9090 Metrics collection and scraping
grafana 3000 Auto-provisioned dashboards (Prometheus datasource)
tempo 4317/4318 Distributed traces collector (OTLP) + query UI 3200

3. Gateway request pipeline

Pure-ASGI middleware wrapping, outside → inside:

SecurityHeaders → RateLimit → Metrics → _app (CORS → Logging → Errors → JWT → Router)
Layer Role
SecurityHeadersMiddleware Sets hardening headers on every response
RateLimitMiddleware Redis sliding-window per IP; tuned per route group (login 10/min, register 5/min, reset-password 3/min, verify-email/resend 5/min, refresh 20/min, sessions 60/min, everything else 100/min) → 429 with Retry-After
MetricsMiddleware Request counters/durations for Prometheus
CORSMiddleware Origin allow-list from CORS_ORIGINS
LoggingMiddleware Structured JSON request logs
ErrorHandlingMiddleware Normalized JSON error responses
JWTAuthMiddleware Extracts Bearer token, calls POST /tokens/verify, injects X-User-Id, strips caller-supplied X-User-Id / X-Internal-Key

The /health endpoint probes all 5 services concurrently and reports ok/degraded. The /ready endpoint additionally checks the rate-limit Redis connection and returns 200/503 for orchestrator readiness probes.

4. Inter-service communication

Synchronous (HTTP, request/response) — always on the internal Docker network, never exposed:

  • Gateway → token-service: POST /tokens/verify
  • auth-service → user-service: profile creation, GET /users/internal/by-email, GET /users/internal/by-id, verification & reset-token endpoints
  • auth-service → token-service: POST /tokens/generate, POST /tokens/revoke
  • auth-service → session-service: POST /sessions/create, validate, rotate, revoke, internal/revoke-all

All internal calls are authenticated with the shared INTERNAL_API_KEY (X-Internal-Key header). The gateway strips this header from any external request (defense in depth).

Asynchronous (Kafka events) — publishes on Redpanda; consumed by email-service:

Topic Emitted on Consumed by
user.registered Registration / verification re-send email-service → verification email
auth.login Successful login (analytics-ready)
auth.new_login Login from a new device email-service → new-login alert (device + IP geolocation)
auth.suspicious Login after near-lockout failures email-service → security alert + reset link
user.deletion_requested Account deletion requested email-service → confirmation link (24 h, single use)
user.email_change_requested Email change requested email-service → confirmation link to the new address (24 h, single use)

The event stream decouples the auth-service from email delivery: if the email-service is down, messages persist in Redpanda and are consumed later.

5. Data ownership

Each service owns its database and never writes to another service's store:

┌────────────────┬─────────────────────────┬────────────────────────────────────────────────┐
│ Service        │ Store                   │ Data                                           │
├────────────────┼─────────────────────────┼────────────────────────────────────────────────┤
│ auth-service   │ postgres-auth           │ credentials (Argon2id hashes)                  │
│ user-service   │ postgres-users          │ profiles, avatars, token hashes, pending_email │
│ session-service│ postgres-sessions       │ sessions, refresh-token hashes                 │
│ token-service  │ redis DB 0              │ access-token blacklist                         │
│ gateway        │ redis DB 2              │ rate-limit sliding windows                     │
│ auth-service   │ redis DB 2              │ brute-force counters                           │
│ email-service  │ (SMTP) / redpanda       │ email templates, event stream                  │
└────────────────┴─────────────────────────┴────────────────────────────────────────────────┘

One-time tokens (email verification, password reset, account deletion, email change) are stored in the user-service only as SHA-256 hashes; the raw token is HMAC-signed on the auth-service side and bound to a purpose, so a DB leak alone cannot forge a token and a token cannot be replayed across flows.

6. Security model

Control Where Detail
Password hashing auth-service Argon2id (with silent rehash on param upgrades)
JWT token-service HS256, signed with JWT_SECRET_KEY; iss=identyx, aud=identyx-api; access token 30 min, refresh token 7 days
Refresh-token rotation session-service Refresh tokens are single-use; each refresh rotates the hash, so a stolen token is invalidated on first use
Brute-force protection auth-service 5 failed attempts → 15 min lockout (Redis DB 2, keyed per account, IP-independent); near-threshold logins raise a auth.suspicious alert
Password confirmation gateway → auth-service Delete-account, email-change and avatar-delete require the current password (422 if missing, 403 if wrong)
Account deletion (GDPR) auth-service + user-service Deletion is email-confirmed: a purpose-bound one-time token must be validated before the credential, profile and sessions are removed
Email change auth-service + user-service New email is stored as pending_email and only applied after a purpose-bound one-time token is confirmed (with uniqueness re-check)
Anti-enumeration auth-service Unknown email, wrong password and lockout all return the same generic 401; resend-verification returns a generic message
One-time tokens auth-service + user-service HMAC signature bound to a purpose (email_verification, password_reset, delete_account, email_change) + SHA-256 stored hash + expiry (24 h) + single use; a token cannot be replayed across flows
Internal API all services X-Internal-Key shared secret; routes hidden from the OpenAPI schema
Header stripping gateway X-User-Id and X-Internal-Key are never trusted from the client
Multi-device limit session-service Default 5 sessions/user; the oldest session is revoked automatically

7. Observability

  • Metrics — every service exposes /metrics (Prometheus); scrape config in infra/prometheus/prometheus.yml.
  • Dashboards — Grafana is auto-provisioned at :3000 (datasource infra/grafana/provisioning/, dashboard infra/grafana/dashboards/): request rate, error rate (5xx), p50/p95 latency and active requests per service.
  • Traces — each service ships OpenTelemetry spans (OTLP/HTTP) to Tempo (:4318); every FastAPI route and outgoing HTTP call is auto-instrumented. Tracing is enabled via OTEL_ENABLED/OTEL_EXPORTER_OTLP_ENDPOINT and is a no-op otherwise.
  • Logs — structured JSON via python-json-logger (service_started, service_stopped, per-request logs, security events).

User flows

All flows are exposed through the gateway at http://localhost:8100. The Component column traces each step across the microservices.

1. Registration

POST /v1/auth/register
Step Action Component
1 Validate payload (email, username, password rules) gateway → auth-service
2 Create the user profile auth-service → user-service
3 Hash the password (Argon2id) and store the credential auth-service → postgres-auth
4 Generate the access/refresh token pair auth-service → token-service
5 Create the session and store the refresh-token hash auth-service → session-service
6 Generate an HMAC verification token, store its hash auth-service → user-service
7 Publish user.registered auth-service → redpanda
8 Consume the event and send the verification email email-service → SMTP (Brevo)
9 Return {access_token, refresh_token, user} gateway

If step 3 fails, the created profile is rolled back (best-effort) to avoid an orphan account.

2. Email verification

GET /v1/auth/verify-email?token=<one-time-token>
Step Action Component
1 Verify the HMAC signature → user_id auth-service
2 Check the stored hash (expiry + not used) auth-service → user-service
3 Mark the token used and the email verified auth-service → user-service
4 Return {message, email, is_verified} gateway

3. Login

POST /v1/auth/login
Step Action Component
0 Check brute-force lockout (Redis DB 2) auth-service
1 Fetch the profile by email (unknown → generic 401) auth-service → user-service
2 Fetch the credential and verify the password (Argon2id) auth-service → postgres-auth
3 On failure: record attempt → lockout at 5 → 401 auth-service → redis
4 On success: reset the failure counter, rehash if needed auth-service
5 Generate the token pair auth-service → token-service
6 Create the session auth-service → session-service
7 Publish auth.login + auth.new_login auth-service → redpanda
8 Send new-login alert (device + IP geolocation) email-service
9 If the account was near lockout: publish auth.suspicious + send security alert with one-time reset link auth-service → email-service
10 Return the token pair and profile gateway

4. Token refresh (rotation)

POST /v1/auth/refresh
Step Action Component
1 Validate the refresh token (single-use) auth-service → session-service
2 Fetch the profile auth-service → user-service
3 Generate a new token pair auth-service → token-service
4 Rotate the session hash (old refresh token is burned) auth-service → session-service
5 Return the new token pair gateway

If an attacker replays a stolen refresh token first, the legitimate client gets a 401 on its next refresh.

5. Logout

POST /v1/auth/logout
Step Action Component
1 Revoke the session auth-service → session-service
2 Blacklist the access token until its natural expiry auth-service → token-service
3 Return {message} gateway

6. Password reset

POST /v1/auth/reset-password    # one-time HMAC token + new password
Step Action Component
1 Verify the HMAC signature → user_id auth-service
2 Check the stored hash (expiry + single use) auth-service → user-service
3 Hash the new password (Argon2id) and update the credential auth-service → postgres-auth
4 Mark the reset token as used auth-service → user-service
5 Revoke all sessions (every device is disconnected) auth-service → session-service

The reset link is delivered through the auth.suspicious / reset email built by the email-service.

7. Session management & multi-device limit

Action Endpoint Detail
List active sessions GET /v1/sessions/ Returns all sessions with device info
Revoke one session DELETE /v1/sessions/{session_id} Owner only
Revoke all sessions DELETE /v1/sessions/revoke-all Signs out every device

When a new login pushes the session count above MAX_SESSIONS_PER_USER (default 5), the oldest session is revoked automatically.

8. Profile & avatar

Action Endpoint Detail
Get current profile GET /v1/users/me From X-User-Id
Get / update / delete profile GET/PATCH/DELETE /v1/users/{user_id} Owner only; delete is email-confirmed and revokes sessions
Upload / get / reset avatar POST/GET/DELETE /v1/users/{user_id}/avatar JPEG/PNG/WebP ≤ 5 MB; old photo replaced; delete requires the current password

9. Account deletion (GDPR, email-confirmed)

Deletion is not immediate — the account is only removed after the owner confirms via a one-time link sent by email (proof of possession, GDPR §17):

POST /v1/users/{user_id}/deletion-request   # + {"password": ...}
POST /v1/auth/confirm-deletion              # + {"token": ...}
Step Action Component
1 Confirm the current password and ownership gateway → auth-service
2 Generate a delete_account one-time token, store its hash, publish user.deletion_requested auth-service → user-service → redpanda
3 Send the confirmation link (24 h, single use) email-service
4 POST /v1/auth/confirm-deletion validates the token gateway → auth-service
5 Delete the credential, revoke all sessions, publish user.deleted auth-service → postgres-auth → session-service
6 Delete the profile + uploaded avatar, mark the token used auth-service → user-service

10. Email change (with re-verification)

The new address is stored as pending_email and only becomes active after the confirmation link is opened (delivered to the new address):

POST /v1/users/{user_id}/email-change        # + {"password": ..., "new_email": ...}
POST /v1/auth/confirm-email-change           # + {"token": ...}
Step Action Component
1 Confirm the current password + ownership; reject same/already-registered emails gateway → auth-service
2 Generate an email_change one-time token, store its hash, publish user.email_change_requested auth-service → user-service → redpanda
3 Send the confirmation link to the new address (24 h, single use) email-service
4 POST /v1/auth/confirm-email-change validates the token, re-checks the new email is free gateway → auth-service
5 Apply the new email, mark it verified, mark the token used auth-service → user-service

Project layout

identyx-api/
├── README.md                         # this file
├── LICENSE                           # MIT
├── pyproject.toml                    # root tooling (ruff, pytest)
├── Makefile                          # dev targets (lint, test, up, down, e2e…)
├── .pre-commit-config.yaml           # ruff hooks (lint + format)
├── .env.example                      # development environment template
├── .env.production.example           # production environment template
├── .github/workflows/ci.yml          # CI pipeline (lint, tests, E2E, images)
│
├── docs/
│   └── api/
│       ├── APIDOG.md                 # Apidog-compatible API documentation
│       └── openapi.json              # exported OpenAPI spec
│
├── scripts/
│   └── e2e_smoke_test.sh             # lightweight CLI E2E smoke test
│
├── infra/                            # deployment & infrastructure
│   ├── docker-compose.yml            # full development stack
│   ├── docker-compose.prod.yml       # standalone production stack (Caddy TLS)
│   ├── Caddyfile                     # HTTPS reverse proxy (production)
│   ├── backup.sh                     # scheduled database backups
│   ├── restore.sh                    # restore databases from a backup
│   ├── prometheus/prometheus.yml     # Prometheus scrape config
│   ├── tempo/tempo.yml               # Tempo (OTLP traces) config
│   ├── grafana/
│   │   ├── provisioning/
│   │   │   ├── datasources/          # Prometheus + Tempo datasources
│   │   │   └── dashboards/           # dashboard provider config
│   │   └── dashboards/               # Identyx service-overview dashboard
│   └── redis/redis.conf              # Redis configuration
│
├── gateway/                          # API Gateway — :8100
│   ├── Dockerfile
│   ├── pyproject.toml
│   ├── tests/
│   └── app/
│       ├── main.py                   # FastAPI app, middleware wiring, /health, /ready, /metrics
│       ├── deps.py                   # shared dependencies (bearer scheme)
│       ├── http.py                   # shared httpx client
│       ├── core/
│       │   ├── config.py             # pydantic-settings configuration
│       │   └── logging/config.py     # structured JSON logging
│       ├── middleware/
│       │   ├── security_headers.py   # hardening headers
│       │   ├── rate_limit.py         # Redis sliding-window limiting
│       │   ├── jwt_auth.py           # Bearer validation + X-User-Id injection
│       │   ├── cors.py               # CORS allow-list
│       │   ├── logging.py            # request logging
│       │   └── errors.py             # normalized errors
│       ├── metrics/prometheus.py     # Prometheus metrics
│       ├── observability/tracing.py  # OpenTelemetry setup (OTLP → Tempo)
│       └── routes/
│           ├── auth.py               # /v1/auth/*  proxy
│           ├── users.py              # /v1/users/* proxy
│           └── sessions.py           # /v1/sessions/* proxy
│
├── services/
│   ├── auth-services/                # authentication — :8002
│   │   ├── Dockerfile
│   │   ├── pyproject.toml
│   │   ├── alembic/                  # DB migrations
│   │   ├── tests/
│   │   └── app/
│   │       ├── main.py               # FastAPI app + Kafka publisher
│   │       ├── core/config.py        # pydantic-settings configuration
│   │       ├── api/routes/auth.py    # register, login, logout, refresh, verify-email, reset-password, resend-verification, deletion + email-change flows
│   │       ├── db/session.py         # async database session
│   │       ├── models/               # SQLAlchemy models (credential)
│   │       ├── repositories/         # credential repository
│   │       ├── schemas/auth.py       # request/response models
│   │       ├── security/
│   │       │   ├── hashing.py        # Argon2id hashing
│   │       │   ├── brute_force.py    # lockout counters (Redis)
│   │       │   └── verification.py   # purpose-bound HMAC one-time tokens
│   │       ├── events/
│   │       │   ├── types.py          # event contracts + topic names
│   │       │   └── publisher.py      # Kafka publisher
│   │       ├── services/auth_service.py  # orchestration logic
│   │       ├── metrics/prometheus.py
│   │       └── observability/tracing.py
│   │
│   ├── user-services/                # profiles & avatars — :8001
│   │   ├── Dockerfile
│   │   ├── pyproject.toml
│   │   ├── alembic/                  # DB migrations (users, verification/reset/deletion/email-change tokens)
│   │   ├── tests/
│   │   └── app/
│   │       ├── main.py
│   │       ├── core/config.py
│   │       ├── api/routes/users.py   # public + /users/internal/* endpoints
│   │       ├── db/session.py
│   │       ├── models/               # user, verification, password_reset, deletion_request, email_change
│   │       ├── repositories/
│   │       ├── schemas/user.py
│   │       ├── services/user_service.py
│   │       ├── storage/              # avatar storage (github upload, base)
│   │       ├── metrics/prometheus.py
│   │       └── observability/tracing.py
│   │
│   ├── token-services/               # JWT lifecycle — :8003
│   │   ├── Dockerfile
│   │   ├── pyproject.toml
│   │   ├── tests/
│   │   └── app/
│   │       ├── main.py
│   │       ├── core/config.py
│   │       ├── api/routes/tokens.py  # generate / verify / revoke (internal only)
│   │       ├── dependencies.py       # require_internal_key
│   │       ├── cache/redis.py        # blacklist storage
│   │       ├── security/jwt.py       # JWT sign/verify (iss/aud checked)
│   │       ├── services/token_service.py
│   │       ├── schemas/token.py
│   │       └── observability/tracing.py
│   │
│   ├── session-services/             # sessions & refresh rotation — :8004
│   │   ├── Dockerfile
│   │   ├── pyproject.toml
│   │   ├── tests/
│   │   └── app/
│   │       ├── main.py
│   │       ├── core/config.py        # MAX_SESSIONS_PER_USER
│   │       ├── api/routes/sessions.py
│   │       ├── db/session.py
│   │       ├── models/session.py
│   │       ├── repositories/session_repo.py
│   │       ├── schemas/session.py
│   │       ├── services/session_service.py
│   │       └── observability/tracing.py
│   │
│   └── email-services/               # transactional emails — :8005
│       ├── Dockerfile
│       ├── pyproject.toml
│       ├── tests/
│       └── app/
│           ├── main.py               # wires handlers to topics
│           ├── core/config.py        # SMTP + Kafka settings
│           ├── api/routes/emails.py  # internal send endpoints
│           ├── events/
│           │   ├── subscriber.py     # Kafka consumer (EventSubscriber)
│           │   ├── handlers.py       # per-topic email handlers
│           │   └── types.py          # topic constants
│           ├── providers/smtp.py     # SMTP transport (Brevo)
│           ├── services/
│           │   ├── email_service.py
│           │   └── ip_geolocation.py # device + IP location in alerts
│           ├── templates/            # verify_email, reset_password, security_alert, new_login, account_deletion, email_change
│           └── observability/tracing.py
│
├── shared/                           # shared cross-service package
│   ├── events/                       # publisher, subscribers, types
│   ├── logging/config.py
│   └── metrics/prometheus.py
│
├── tests/
│   └── e2e/
│       └── test_full_flow.py         # end-to-end tests (12 scenarios)
└── avatars/default.png               # default avatar asset

Quick start

Prerequisites

  • Python 3.14+
  • uv
  • Docker & Docker Compose

Local development

# 1. Clone the repository
git clone https://github.com/DarcinBig/identyx-api.git
cd identyx-api

# 2. Configure the environment (dev defaults; fill the secrets)
cp .env.example .env

# 3. Start the full stack (all services + PostgreSQL, Redis, Redpanda, Prometheus)
docker compose -f infra/docker-compose.yml up -d --build

The gateway runs on http://localhost:8100 — all requests go through it. Interactive docs (Swagger UI) are available at http://localhost:8100/docs, Redpanda Console at http://localhost:8180, Prometheus at http://localhost:9090, Grafana at http://localhost:3000 (dashboards auto-provisioned) and Tempo at http://localhost:3200 (traces).

Developer convenience targets are available through the root Makefile (make lint, make test, make check, make up, make down, make e2e). Pre-commit hooks (ruff) are configured in .pre-commit-config.yaml.

Unit tests live in <service>/tests/unit/ (no Docker required): run them with uv run pytest from a service directory, or all services with make test. The E2E suite in tests/e2e/ requires a running stack and is skipped otherwise.

To develop a single service locally (outside Docker) instead, start the stack and then run the service with uv run uvicorn app.main:app --reload --port <port> from its directory — see the per-service READMEs.

Production (Docker)

infra/docker-compose.prod.yml is a standalone production stack. Only Caddy (ports 80/443) is exposed; every service stays on the internal Docker network. Caddy terminates HTTPS automatically with Let's Encrypt certificates.

Deploy on a VPS:

# 1. Point a DNS A record (e.g. api.identyx.io) at the host, then:

# 2. Prepare the production environment (fresh secrets!)
cp .env.production.example .env
#    → edit .env: DOMAIN, APP_BASE_URL, FRONTEND, CORS_ORIGINS,
#      then generate every secret with `openssl rand`:
#        openssl rand -base64 32   # POSTGRES_PASSWORD, REDIS_PASSWORD
#        openssl rand -hex 64      # JWT_SECRET_KEY
#        openssl rand -base64 48   # INTERNAL_API_KEY

# 3. Pull the pre-built images (or add `--build` to build locally)
IMAGE_TAG=v1.0.0 docker compose -f infra/docker-compose.prod.yml pull

# 4. Start the stack
IMAGE_TAG=v1.0.0 docker compose -f infra/docker-compose.prod.yml up -d

# 5. Check health
curl https://api.identyx.io/health

Backups (scheduled via cron):

0 2 * * * /path/to/identyx/infra/backup.sh >> /var/log/identyx-backup.log 2>&1

Restore (from the latest backup, or pass a specific file):

/path/to/identyx/infra/restore.sh                # latest backup in infra/backups/
/path/to/identyx/infra/restore.sh backups/db-users-2026-08-10_020001.sql.gz

Notes:

  • Swagger/OpenAPI is disabled in production (ENVIRONMENT=production).
  • Images are pulled from ghcr.io/darcinbig/; pin a version with IMAGE_TAG.
  • The dev compose (infra/docker-compose.yml) exposes ports for local debugging only — never use it on a public host.

Environment variables

The shared configuration lives in the root .env (loaded by every container via env_file). Templates:

  • .env.example — development defaults.
  • .env.production.example — production template (fresh secrets required).

Key variables:

Variable Description Default
POSTGRES_USER / POSTGRES_PASSWORD Database credentials (shared) identyx / (required)
JWT_SECRET_KEY HMAC secret for JWT (≥ 64 chars in prod) (required)
REDIS_PASSWORD Redis auth password (required)
INTERNAL_API_KEY Shared secret for inter-service calls (X-Internal-Key) (required)
APP_BASE_URL Public base URL used in email links http://localhost:8100
ENVIRONMENT development or production development
CORS_ORIGINS Comma-separated allowed origins http://localhost:3000,http://localhost:8000
RATE_LIMIT_REFRESH / RATE_LIMIT_SESSIONS Per-IP rate limits for refresh & sessions routes 20 / 60
BRUTE_FORCE_MAX_ATTEMPTS / BRUTE_FORCE_LOCKOUT_MINUTES Login lockout policy (per account) 5 / 15
GRAFANA_ADMIN_USER / GRAFANA_ADMIN_PASSWORD Grafana admin credentials admin / admin (change in prod)
OTEL_ENABLED Enable OpenTelemetry tracing true
OTEL_EXPORTER_OTLP_ENDPOINT OTLP/HTTP collector (Tempo) http://tempo:4318

API Reference

All public endpoints are versioned under /v1. Protected endpoints expect Authorization: Bearer <access_token>. Swagger is disabled in production — use the OpenAPI export in docs/api/APIDOG.md (Apidog) or /docs on a dev instance.

Endpoint Method Auth Description
/v1/auth/register POST Create account, returns JWT pair, sends verification email
/v1/auth/login POST Login, returns JWT pair, sends new-login alert email
/v1/auth/logout POST JWT Revoke session + blacklist access token
/v1/auth/refresh POST Rotate the refresh token (single-use)
/v1/auth/verify-email GET Verify email via one-time HMAC token (?token=)
/v1/auth/reset-password POST Set a new password with a one-time reset token
/v1/auth/resend-verification POST Re-send the verification email (anti-enumeration)
/v1/auth/confirm-deletion POST Confirm account deletion via one-time email link
/v1/auth/confirm-email-change POST Confirm the new email via one-time email link
/v1/users/me GET JWT Current user profile
/v1/users/{user_id} GET JWT User profile (owner only)
/v1/users/{user_id} PATCH JWT Update profile (owner only)
/v1/users/{user_id} DELETE JWT Delete account (email-confirmed; requires password)
/v1/users/{user_id}/deletion-request POST JWT Request account deletion (requires password)
/v1/users/{user_id}/email-change POST JWT Request an email change (requires password + new_email)
/v1/users/{user_id}/avatar POST JWT Upload/replace avatar (multipart/form-data)
/v1/users/{user_id}/avatar GET JWT Current avatar URL
/v1/users/{user_id}/avatar DELETE JWT Remove avatar (requires password)
/v1/sessions/ GET JWT List active sessions
/v1/sessions/revoke-all DELETE JWT Revoke all sessions
/v1/sessions/{session_id} DELETE JWT Revoke a session (owner only)
/health GET Service health (incl. downstream probes)
/ready GET Readiness probe (200/503) for orchestrators
/metrics GET Prometheus scrape endpoint

The internal routes (/tokens/*, /users/internal/*, /emails/*) are not exposed through the gateway.


Tech Stack

Layer Technology
Language Python 3.14
Framework FastAPI, uvicorn
Runtime Docker, Docker Compose
Databases PostgreSQL 16 (per service), Redis 7
Auth JWT (HS256), Argon2id
Messaging Kafka/Redpanda (event-driven inter-service communication)
Monitoring Prometheus, Grafana, OpenTelemetry + Tempo, structured JSON logs
CI / CD GitHub Actions (lint, unit tests, E2E, GHCR images), GHCR
Linting Ruff (pre-commit hooks, Makefile targets)

Contributing

We welcome contributions! Please see CONTRIBUTING.md for guidelines on:

  • Setting up a development environment
  • Code style & linting
  • Writing tests
  • Opening issues and pull requests

Roadmap

  • Email / password authentication
  • Email verification (HMAC token)
  • JWT access & refresh token rotation
  • Session management & revocation
  • Multi-device session limit (oldest session revoked)
  • New-login email alerts with device + IP geolocation
  • User profiles & avatar upload
  • GDPR account deletion (email-confirmed)
  • Email change with re-verification
  • Password confirmation on sensitive actions
  • Prometheus metrics + Grafana dashboards
  • OpenTelemetry distributed traces (Tempo)
  • CI pipeline with E2E suite
  • OAuth 2.0 (Google, GitHub, Apple, etc.)
  • Passkeys (WebAuthn)
  • Multi-factor authentication (TOTP)
  • Admin API & dashboard
  • Audit log
  • Etc.

License

MIT © DarcinBig

About

Identyx is a production-ready authentication and identity API built with FastAPI. It supports email/password, OAuth providers, passkeys (WebAuthn), and MFA. Designed for security and scalability, it enables seamless user management, token-based auth, sessions, and integration across web and mobile apps.

Topics

Resources

Contributing

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages