A production-ready microservice for face-based authentication using FastAPI, PostgreSQL with pgvector, and advanced security features including liveness detection, rate limiting, and comprehensive monitoring.
- Face Recognition Authentication: 128-dimensional face embeddings using
face_recognitionlibrary - pgvector Integration: Efficient vector similarity search with HNSW indexes
- Liveness Detection: Passive anti-spoofing to prevent photo/video attacks
- Rate Limiting: Per-IP and per-endpoint rate limiting with Redis
- JWT Authentication: Secure token-based authentication
- Prometheus Metrics: Comprehensive monitoring and observability
- Structured Logging: JSON logging with correlation IDs
- Fraud Detection: Track and analyze failed login attempts
- PostgreSQL + AsyncIO: High-performance async database operations
- Docker Support: Easy deployment with Docker Compose
flowchart TB
subgraph Client["Client Layer"]
WebUI["Web UI<br/>(WebRTC/HTML)"]
MobileApp["Mobile App"]
CLI["CLI/Test Script<br/>(capture_test.py)"]
end
subgraph API["API Layer (FastAPI)"]
direction TB
AuthEndpoints["Auth Endpoints<br/>(/register, /login)"]
AdminEndpoints["Admin Endpoints<br/>(/failed-attempts)"]
HealthEndpoints["Health Endpoints<br/>(/health, /ready)"]
Middleware["Middleware<br/>(CORS, Logging, Metrics)"]
RateLimiter["Rate Limiter<br/>(slowapi + Redis)"]
end
subgraph Services["Business Logic Layer"]
direction TB
FaceUtils["Face Recognition<br/>(face_recognition lib)"]
LivenessDetect["Liveness Detection<br/>(Anti-spoofing)"]
Security["Security Service<br/>(JWT, Password Hash)"]
Metrics["Metrics Service<br/>(Prometheus)"]
end
subgraph Data["Data Layer"]
direction LR
PostgreSQL[("PostgreSQL<br/>+ pgvector<br/>(Face Embeddings)")]
Redis[("Redis<br/>(Rate Limiting)")]
Logs[("Logs<br/>(JSON Files)")]
end
WebUI --> Middleware
MobileApp --> Middleware
CLI --> Middleware
Middleware --> RateLimiter
RateLimiter --> AuthEndpoints
RateLimiter --> AdminEndpoints
Middleware --> HealthEndpoints
AuthEndpoints --> FaceUtils
AuthEndpoints --> LivenessDetect
AuthEndpoints --> Security
AdminEndpoints --> Security
FaceUtils --> PostgreSQL
LivenessDetect --> PostgreSQL
Security --> PostgreSQL
RateLimiter --> Redis
Metrics --> Logs
Middleware --> Metrics
HealthEndpoints -.->|Check| PostgreSQL
HealthEndpoints -.->|Check| Redis
style API fill:#e1f5ff
style Services fill:#fff4e1
style Data fill:#ffe1e1
style Client fill:#e8f5e9
sequenceDiagram
autonumber
actor User
participant WebUI as Web UI
participant API as FastAPI Server
participant RateLimit as Rate Limiter
participant Liveness as Liveness Detector
participant FaceRec as Face Recognition
participant PgVector as PostgreSQL<br/>+ pgvector
participant Redis
participant Metrics as Prometheus
User->>WebUI: Capture face image
WebUI->>API: POST /auth/login<br/>(face_image)
API->>RateLimit: Check rate limit
RateLimit->>Redis: GET request count
Redis-->>RateLimit: Return count
alt Rate limit exceeded
RateLimit-->>API: 429 Too Many Requests
API-->>WebUI: Rate limit error
API->>Metrics: Record rate_limit_hit
else Within limit
RateLimit->>Redis: Increment counter
API->>Liveness: Detect liveness
Liveness->>Liveness: Analyze texture,<br/>color, brightness
alt Not live (spoofing detected)
Liveness-->>API: is_live=false, score
API->>PgVector: Record failed attempt
API-->>WebUI: 401 Unauthorized<br/>(Liveness failed)
API->>Metrics: Record liveness_check_failed
else Live face detected
Liveness-->>API: is_live=true, score
API->>Metrics: Record liveness_check_success
API->>FaceRec: Extract face embedding
FaceRec-->>API: 128-dim vector
API->>PgVector: Vector similarity search<br/>(HNSW index, cosine distance)
Note over PgVector: SELECT * FROM users<br/>WHERE is_active = true<br/>ORDER BY face_embedding <=> ?<br/>LIMIT 1
PgVector-->>API: Best match + distance
alt Match found (distance < threshold)
API->>API: Generate JWT tokens
API->>Metrics: Record login_success
API-->>WebUI: 200 OK<br/>{access_token, user, confidence}
WebUI-->>User: Login successful
else No match / below threshold
API->>PgVector: Record failed attempt<br/>(reason: no_match)
API->>Metrics: Record login_failed
API-->>WebUI: 401 Unauthorized<br/>(Face not recognized)
end
end
end
flowchart TD
Start([User Starts Registration]) --> Capture[Capture Face Image]
Capture --> Submit[Submit Form:<br/>username, email,<br/>password, face_image]
Submit --> RateCheck{Rate Limit<br/>Check}
RateCheck -->|Exceeded| RateError[Return 429<br/>Too Many Requests]
RateError --> End([End])
RateCheck -->|OK| ValidateInput{Validate Input}
ValidateInput -->|Invalid| ValidationError[Return 422<br/>Validation Error]
ValidationError --> End
ValidateInput -->|Valid| CheckExists{User Exists?}
CheckExists -->|Yes| ExistsError[Return 400<br/>User Already Exists]
ExistsError --> End
CheckExists -->|No| LivenessCheck[Liveness Detection]
LivenessCheck --> LivenessDecision{Is Live?}
LivenessDecision -->|No| LivenessError[Return 401<br/>Spoofing Detected]
LivenessError --> RecordAttempt1[Record Failed Attempt]
RecordAttempt1 --> End
LivenessDecision -->|Yes| ExtractFace[Extract Face<br/>from Image]
ExtractFace --> FaceFound{Face Found?}
FaceFound -->|No| NoFaceError[Return 400<br/>No Face Detected]
NoFaceError --> End
FaceFound -->|Yes| QualityCheck{Image Quality<br/>Check}
QualityCheck -->|Poor| QualityError[Return 400<br/>Poor Image Quality]
QualityError --> End
QualityCheck -->|Good| ExtractEmbedding[Extract 128-dim<br/>Face Embedding]
ExtractEmbedding --> HashPassword[Hash Password<br/>with bcrypt]
HashPassword --> CreateUser[Create User Record<br/>in PostgreSQL]
CreateUser --> StoreEmbedding[Store Face Embedding<br/>as Vector in pgvector]
StoreEmbedding --> CreateIndex[Create/Update<br/>HNSW Index]
CreateIndex --> GenerateTokens[Generate JWT<br/>Access & Refresh Tokens]
GenerateTokens --> RecordMetrics[Record Success Metrics]
RecordMetrics --> Success[Return 201<br/>Registration Successful]
Success --> End
style Start fill:#e8f5e9
style End fill:#ffebee
style Success fill:#c8e6c9
style RateError fill:#ffcdd2
style ValidationError fill:#ffcdd2
style ExistsError fill:#ffcdd2
style LivenessError fill:#ffcdd2
style NoFaceError fill:#ffcdd2
style QualityError fill:#ffcdd2
style LivenessCheck fill:#fff9c4
style ExtractEmbedding fill:#b3e5fc
style StoreEmbedding fill:#b3e5fc
graph TB
subgraph "FastAPI Application"
Main[main.py<br/>App Initialization]
subgraph "Configuration"
Config[config.py<br/>Settings Management]
Env[.env<br/>Environment Variables]
end
subgraph "Routes"
AuthRoutes[auth.py<br/>Registration & Login]
AdminRoutes[admin.py<br/>Fraud Detection]
HealthRoutes[health.py<br/>Health Checks]
end
subgraph "Middleware"
LogMiddleware[logging.py<br/>Request Logging]
MetricsMiddleware[metrics.py<br/>HTTP Metrics]
CORSMiddleware[CORS<br/>Cross-Origin]
end
subgraph "Services"
FaceService[face_utils.py<br/>Face Recognition]
LivenessService[liveness.py<br/>Anti-spoofing]
SecurityService[security.py<br/>JWT & Passwords]
MetricsService[metrics.py<br/>Prometheus]
end
subgraph "Data Access"
Database[database.py<br/>SQLAlchemy Async]
Models[models.py<br/>User & Attempt Models]
Schemas[schemas.py<br/>Pydantic Validation]
end
subgraph "Dependencies"
Deps[dependencies.py<br/>DI Container]
end
end
subgraph "External Services"
PG[(PostgreSQL<br/>+ pgvector)]
RedisDB[(Redis)]
PrometheusDB[(Prometheus)]
end
subgraph "Infrastructure"
Alembic[Alembic<br/>Migrations]
Docker[Docker Compose]
Logs[JSON Logs]
end
Main --> Config
Main --> AuthRoutes
Main --> AdminRoutes
Main --> HealthRoutes
Main --> LogMiddleware
Main --> MetricsMiddleware
Main --> CORSMiddleware
Config --> Env
AuthRoutes --> Deps
AdminRoutes --> Deps
Deps --> FaceService
Deps --> LivenessService
Deps --> SecurityService
Deps --> Database
AuthRoutes --> MetricsService
AdminRoutes --> MetricsService
FaceService --> Models
LivenessService --> Models
SecurityService --> Models
Database --> Models
Database --> Schemas
Database --> PG
MetricsService --> PrometheusDB
LogMiddleware --> Logs
AuthRoutes --> RedisDB
Alembic --> PG
Docker --> PG
Docker --> RedisDB
style Main fill:#4fc3f7
style Config fill:#fff59d
style Database fill:#ef5350
style MetricsService fill:#66bb6a
style FaceService fill:#ab47bc
style LivenessService fill:#ab47bc
erDiagram
users ||--o{ failed_login_attempts : has
users {
uuid id PK
string username UK
string email UK
string password_hash
vector_128 face_embedding "pgvector Vector(128)"
string model_version "face_recognition version"
boolean is_active
boolean is_locked
timestamp locked_until
timestamp created_at
timestamp updated_at
}
failed_login_attempts {
uuid id PK
uuid user_id FK
string ip_address
string user_agent
string reason "no_match|liveness_failed|rate_limit"
float confidence_score
float liveness_score
timestamp attempted_at
}
pgvector HNSW Index:
CREATE INDEX face_embedding_hnsw_idx ON users
USING hnsw (face_embedding vector_cosine_ops);Vector Similarity Search:
SELECT * FROM users
WHERE is_active = true
AND NOT is_locked
ORDER BY face_embedding <=> '[0.1, 0.2, ...]'::vector
LIMIT 1;- Python 3.12+
- PostgreSQL 16+ with pgvector extension
- Redis 7+
- Docker & Docker Compose (optional)
# Clone repository
cd simple-faceid
# Create and activate virtual environment
python3.12 -m venv .venv
source .venv/bin/activate # On Windows: .venv\Scripts\activate
# Install dependencies
pip install -r requirements.txt# Copy example environment file
cp .env.example .env
# Edit .env and set required values:
# - SECRET_KEY: Generate a secure random string (min 32 chars)
# - DATABASE_URL: PostgreSQL connection string
# - REDIS_URL: Redis connection stringGenerate a secure secret key:
python -c "import secrets; print(secrets.token_urlsafe(32))"# Install PostgreSQL with pgvector
# On macOS:
brew install postgresql@16 pgvector
# On Ubuntu:
sudo apt-get install postgresql-16 postgresql-16-pgvector
# Create database
createdb faceid_db
# Run migrations
alembic upgrade head# On macOS:
brew services start redis
# On Ubuntu:
sudo systemctl start redis
# Or use Docker:
docker run -d -p 6379:6379 redis:7-alpineThe easiest way to run the service:
# Build and start all services
docker-compose up --build
# Run in background
docker-compose up -d
# View logs
docker-compose logs -f app
# Stop services
docker-compose downServices will be available at:
- API: http://localhost:8000
- API Docs: http://localhost:8000/docs
- Metrics: http://localhost:8000/metrics
- PostgreSQL: localhost:5432
- Redis: localhost:6379
# With auto-reload
uvicorn app.main:app --reload --host 0.0.0.0 --port 8000
# Or using Python directly
python -m app.main# Update .env:
ENVIRONMENT=production
DEBUG=false
RELOAD=false
WORKERS=4
# Run with Gunicorn
gunicorn app.main:app -w 4 -k uvicorn.workers.UvicornWorker --bind 0.0.0.0:8000Once running, visit:
- Swagger UI: http://localhost:8000/docs
- ReDoc: http://localhost:8000/redoc
Register User
POST /api/v1/auth/register
Content-Type: multipart/form-data
Fields:
- username: string
- email: string
- password: string
- face_image: file (JPEG/PNG)Login with Face
POST /api/v1/auth/login
Content-Type: multipart/form-data
Fields:
- face_image: file (JPEG/PNG)
Returns:
- access_token: JWT token
- user: User object
- liveness_score: float
- confidence_score: floatGET /api/v1/admin/failed-attempts?hours=24
GET /api/v1/admin/suspicious-activity?threshold=5
GET /api/v1/admin/metrics-summaryGET /health # Basic health check
GET /ready # Readiness check (DB, Redis, pgvector)
GET /metrics # Prometheus metrics- Start the service
- Open
static/index.htmlin your browser - Allow camera access
- Capture your face image
- Submit for registration or login
# Install OpenCV
pip install opencv-python requests
# Run capture script
python capture_test.pyAccess metrics at http://localhost:8000/metrics
Key Metrics:
faceid_registration_attempts_total- Registration attempts by statusfaceid_login_attempts_total- Login attempts by status/reasonfaceid_liveness_checks_total- Liveness detection resultsfaceid_face_match_latency_seconds- Face matching performancefaceid_vector_search_duration_seconds- Vector search performancefaceid_rate_limit_hits_total- Rate limit violationsfaceid_active_users_total- Active user countfaceid_locked_accounts_total- Locked account count
Structured JSON logs in logs/app.log:
{
"timestamp": "2026-02-01 12:00:00",
"level": "INFO",
"logger": "app.routes.auth",
"message": "User logged in successfully",
"correlation_id": "abc-123-def",
"user_id": "user-uuid",
"environment": "production"
}- Registration: 5 requests/hour per IP
- Login: 10 requests/minute per IP
- Default: 100 requests/hour per IP
- Track failed login attempts by IP and user
- Automatic account locking after 10 failed attempts in 1 hour
- Lock duration: 30 minutes (configurable)
- Failed attempt retention: 30 days
- Passive detection (single image)
- Texture analysis and quality checks
- Configurable confidence threshold (default: 0.7)
- Can be extended with ML models (ONNX)
- 128-dimensional embeddings
- Cosine distance similarity
- Configurable tolerance (default: 0.6)
- HNSW index for fast vector search
- Metadata pre-filtering for scalability
All configuration via environment variables. See .env.example for full list.
Key Settings:
# Face Recognition
FACE_RECOGNITION_TOLERANCE=0.6 # Lower = stricter matching
FACE_DETECTION_MODEL=hog # hog (fast) or cnn (accurate)
# Liveness Detection
LIVENESS_ENABLED=true
LIVENESS_THRESHOLD=0.7 # Higher = stricter
# Rate Limiting
RATE_LIMIT_REGISTER=5/hour
RATE_LIMIT_LOGIN=10/minute
# Security
ACCESS_TOKEN_EXPIRE_MINUTES=30
MAX_FAILED_LOGIN_ATTEMPTS=10simple-faceid/
βββ app/
β βββ __init__.py
β βββ main.py # FastAPI application
β βββ config.py # Pydantic settings
β βββ database.py # Database setup
β βββ models.py # SQLAlchemy models
β βββ schemas.py # Pydantic schemas
β βββ security.py # JWT & password hashing
β βββ dependencies.py # FastAPI dependencies
β βββ face_utils.py # Face recognition logic
β βββ liveness.py # Liveness detection
β βββ logging_config.py # Structured logging
β βββ metrics.py # Prometheus metrics
β βββ middleware/
β β βββ logging.py # Request logging
β β βββ metrics.py # Metrics collection
β βββ routes/
β βββ auth.py # Authentication endpoints
β βββ admin.py # Admin endpoints
β βββ health.py # Health checks
βββ alembic/
β βββ versions/
β β βββ 001_initial.py # Initial migration
β βββ env.py # Alembic config
βββ tests/
β βββ conftest.py # Pytest fixtures
β βββ test_auth.py # Auth tests
βββ static/
β βββ index.html # WebRTC test page
βββ docker/
β βββ postgres-init.sh # PostgreSQL init script
βββ requirements.txt
βββ .env.example
βββ docker-compose.yml
βββ Dockerfile
βββ README.md
# Install test dependencies
pip install pytest pytest-asyncio pytest-cov httpx
# Run tests
pytest
# With coverage
pytest --cov=app --cov-report=html
# Run specific test
pytest tests/test_auth.py -v- HNSW Index: Already configured for fast similarity search
- Pre-filtering: Enabled by default (filter by is_active before vector search)
- Limit Results: Only fetch top 5 candidates (configurable)
-- Monitor query performance
EXPLAIN ANALYZE
SELECT * FROM users
ORDER BY face_embedding <=> '[...]'
LIMIT 5;
-- Adjust HNSW parameters for your data size
CREATE INDEX idx_users_face_embedding_hnsw
ON users USING hnsw (face_embedding vector_cosine_ops)
WITH (m = 16, ef_construction = 64);- < 10K users: Current setup works great
- 10K - 100K users: Increase DB connection pool, add Redis cluster
- 100K - 1M users: Consider read replicas, cache frequently accessed embeddings
- > 1M users: Shard by user cohorts, implement caching layer
pgvector extension not found:
# Install pgvector for your PostgreSQL version
# Then in psql:
CREATE EXTENSION vector;Face recognition installation issues:
# Install system dependencies first
# macOS:
brew install cmake
# Ubuntu:
sudo apt-get install cmake build-essential
# Then reinstall
pip install --upgrade --force-reinstall face-recognitionRedis connection error:
# Check Redis is running
redis-cli ping # Should return "PONG"
# Or start Redis
brew services start redis # macOS
sudo systemctl start redis # LinuxMIT License - see LICENSE file for details
Contributions welcome! Please read CONTRIBUTING.md for guidelines.
For issues and questions:
- GitHub Issues: Create an issue
- Documentation: See /docs
Built with β€οΈ using FastAPI, PostgreSQL, and pgvector