A hardened, security-focused REST API built with FastAPI, SQLAlchemy, Argon2id, and PyJWT. Demonstrates modern defensive software engineering and cybersecurity practices across authentication, anti-IDOR authorization, request rate limiting, HTTP security headers, and structured logging with automated credential redaction.
CRITICAL SECURITY NOTICE:
This API and accompanying security tools are strictly designed for local development testing, academic portfolio review, and defensive hardening on localhost (127.0.0.1) or systems explicitly owned by the user. Do not expose this service to untrusted public networks without configuring production TLS and rotating secret keys.
- What It Does
- Architecture & Defensive Controls
- Security Implementations
- Installation & Setup
- Running the Application
- Usage Guide & Demo Output
- Running Tests & Linting
- Docker Deployment
- Threat Model
- Limitations & Roadmap
- License
secure-api provides a multi-tenant notes management service where every operation is guarded by defensive security controls:
- User Registration & Login:
- Enforces minimum length and complexity rules, combined with a dictionary check against common and breached passwords (incorporating NIST SP 800-63B recommendations).
- Hashes passwords using Argon2id (the PHC winner and OWASP #1 recommended password hashing algorithm).
- Neutralizes timing-based username enumeration during login using constant-time dummy verifications.
- Issues cryptographically signed JWT access tokens with strict algorithm whitelisting (
HS256).
- Multi-Tenant Notes CRUD (Anti-IDOR / Anti-BOLA):
- Eliminates Insecure Direct Object References: notes can only be viewed, updated, or deleted by their verified creator.
- Cross-tenant access attempts return HTTP 404 rather than 403 to prevent attackers from enumerating valid resource IDs.
- Bounded pagination limits (
limit <= 100) to prevent denial-of-service via resource exhaustion.
- Defensive Middleware:
- Sliding-Window Rate Limiter: Throttles failed and rapid login attempts per client IP to mitigate brute-force and credential stuffing attacks (HTTP 429 with
Retry-After). - HTTP Security Headers: Injects
X-Frame-Options: DENY,X-Content-Type-Options: nosniff,Content-Security-Policy, and strips server fingerprinting headers. - Sensitive Data Log Redaction: Custom logging filter automatically masks passwords and Bearer tokens before writing to standard output or log sinks.
- Sliding-Window Rate Limiter: Throttles failed and rapid login attempts per client IP to mitigate brute-force and credential stuffing attacks (HTTP 429 with
[ Incoming HTTP Request ]
│
▼
┌───────────────────────────┐
│ SecurityHeadersMiddleware │ <-- CSP, X-Frame-Options: DENY, nosniff
└─────────────┬─────────────┘
│
▼
┌───────────────────────────┐
│ RequestLoggingMiddleware │ <-- Latency & audit trail
│ + SensitiveDataFilter │ <-- Automatically redacts passwords & tokens
└─────────────┬─────────────┘
│
▼
┌───────────────────────────┐
│ LoginRateLimiter │ <-- Sliding-window limiter on /auth/login
└─────────────┬─────────────┘
│
▼
┌───────────────────────────┐
│ Pydantic Validation │ <-- Strict schema & complexity rules
└─────────────┬─────────────┘
│
▼
┌───────────────────────────┐
│ get_current_user (JWT) │ <-- Validates sub, exp, nbf, and active status
└─────────────┬─────────────┘
│
▼
┌───────────────────────────┐
│ Anti-IDOR Scoped Query │ <-- filter(Note.id == id, Note.owner_id == uid)
└───────────────────────────┘
Modern NIST guidelines (SP 800-63B §5.1.1.2) discourage forced character composition rules (e.g. arbitrarily requiring uppercase, lowercase, numbers, and symbols) because they frequently drive users toward predictable, easily guessed patterns (like Password1!). Instead, NIST recommends:
- Enforcing adequate minimum length (at least 8 characters).
- Comparing chosen passwords against a dictionary of commonly used, leaked, or compromised passwords.
In app/schemas/auth.py, secure-api implements an explicit sample blocklist of frequently targeted/breached passwords alongside minimum length checks and complexity validation for defense-in-depth.
Note on Blocklist Scope: The included blocklist is a representative sample list for local demonstration and automated testing; production systems should check chosen passwords against a comprehensive breached-password dataset, such as the Have I Been Pwned (HIBP) API using k-Anonymity.
A common flaw in rate limit implementations is naively reading the X-Forwarded-For HTTP header. If an application blindly trusts this header, an attacker can trivially bypass rate limits by attaching random IP strings (X-Forwarded-For: 1.2.3.4) to every request.
In secure-api, app/middleware/rate_limiter.py resolves client identity directly from the low-level ASGI connection socket (request.client.host). Header spoofing attempts have zero effect on rate enforcement. When running behind an enterprise reverse proxy (e.g. AWS ALB, Cloudflare, NGINX), proxy header rewriting must be enabled at the ASGI server layer (uvicorn --proxy-headers --forwarded-allow-ips='<trusted_proxy_ip>'), ensuring only verified proxy IPs can rewrite the upstream client address.
The application reads its signing secret from the SECRET_KEY environment variable. To prevent key leaks:
- Pydantic validates that the key has at least 32 characters of cryptographic entropy.
- In non-production environments, if the default development key is detected, the application logs a prominent security warning banner.
- If
ENVIRONMENT=production, the application refuses to start and raises a fatal exception if the default insecure key is still configured.
To prevent Broken Object Level Authorization (OWASP API1:2023):
- Note ownership is derived strictly from the verified JWT
subclaim, not caller-supplied request body fields. - All database queries combine object ID and owner ID:
db.query(Note).filter(Note.id == note_id, Note.owner_id == current_user.id). - Unauthorized cross-tenant queries return
HTTP 404 Not Foundrather thanHTTP 403 Forbiddento neutralize resource existence enumeration.
- Python 3.12+
- Git
- Optional:
uvfor fast dependency management
git clone https://github.com/ErenYeager1836/secure-api.git
cd secure-apiUsing standard Python venv:
python -m venv .venv
# On Linux / macOS:
source .venv/bin/activate
# On Windows (PowerShell):
.\.venv\Scripts\Activate.ps1Or using uv:
uv venv .venv
.\.venv\Scripts\activatepip install -r requirements.txtCopy the example environment configuration:
cp .env.example .envIn local development, the default values in
.env.exampleallow testing immediately. For production, generate a strong random secret withpython -c "import secrets; print(secrets.token_hex(32))".
Start the local development server bound strictly to localhost (127.0.0.1:8000):
python -m uvicorn app.main:app --host 127.0.0.1 --port 8000 --reloadThe server will initialize SQLite database tables automatically on startup.
- Interactive API Documentation: http://127.0.0.1:8000/docs
- Health Check: http://127.0.0.1:8000/health
The following outputs are verbatim responses captured from a live test instance:
curl -X POST http://127.0.0.1:8000/api/v1/auth/register \
-H "Content-Type: application/json" \
-d '{
"email": "analyst@example.com",
"username": "sec_analyst",
"password": "CorrectHorseBatteryStaple2026!"
}'Response (HTTP 201 Created):
{
"id": 1,
"email": "analyst@example.com",
"username": "sec_analyst",
"is_active": true,
"created_at": "2026-10-21T08:51:49.760222"
}curl -X POST http://127.0.0.1:8000/api/v1/auth/login \
-H "Content-Type: application/json" \
-d '{
"username_or_email": "sec_analyst",
"password": "CorrectHorseBatteryStaple2026!"
}'Response (HTTP 200 OK):
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "bearer",
"expires_in": 1800
}TOKEN="<paste_access_token_here>"
curl -X POST http://127.0.0.1:8000/api/v1/notes \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"title": "Incident Triage Notes",
"content": "Observed unusual outbound connections from workstation 4."
}'Response (HTTP 201 Created):
{
"id": 1,
"title": "Incident Triage Notes",
"content": "Observed unusual outbound connections from workstation 4.",
"owner_id": 1,
"created_at": "2026-10-21T08:51:49.945585",
"updated_at": "2026-10-21T08:51:49.945585"
}If another user (attacker_bob, user_id=2) attempts to read, edit, or delete Note #1:
ATTACKER_TOKEN="<user_2_token>"
curl -X GET http://127.0.0.1:8000/api/v1/notes/1 \
-H "Authorization: Bearer $ATTACKER_TOKEN"Response (HTTP 404 Not Found):
{
"detail": "Note not found"
}Returning
404 Not Foundrather than403 Forbiddenprevents attackers from probing and confirming the existence of victim notes.
If a client submits more than 5 rapid login attempts within 60 seconds:
curl -X POST http://127.0.0.1:8000/api/v1/auth/login \
-H "Content-Type: application/json" \
-d '{"username_or_email": "admin", "password": "wrong"}'Response (HTTP 429 Too Many Requests):
{
"detail": "Rate limit exceeded: maximum 5 login attempts per 60s. Try again in 60 seconds."
}Header: Retry-After: 60
pytest -vOutput:
======================== 28 passed, 1 warning in 7.79s ========================
Scans Python AST for security vulnerabilities (injection, weak crypto, unsafe deserialization):
bandit -r app -llOutput:
Test results:
No issues identified.
Code scanned:
Total lines of code: 1102
Run metrics:
Total issues (by severity): Medium: 0, High: 0
flake8 app tests --max-line-length=120Build and run using the security-hardened, non-root Docker container:
# Build Docker image
docker build -t secure-api .
# Run container bound to localhost:8000
docker run -d --name secure_api_instance -p 127.0.0.1:8000:8000 secure-api
# Inspect container security health check
docker ps --filter "name=secure_api_instance"A dedicated threat modeling document following the STRIDE methodology and mapped to the OWASP API Security Top 10 (2023) is available in THREAT_MODEL.md.
In adherence to clean scope engineering, this initial version prioritizes robust core security primitives. The following enhancements are tracked on the roadmap:
- In-Memory Rate Limiter: The sliding window rate limiter stores state in process memory. If scaled across multiple worker processes or containers, each process maintains an independent counter.
- Stateless JWT Revocation: Tokens remain valid until their expiration timestamp (
exp). There is currently no server-side token revocation or blocklist. - Single Factor Authentication: Supports email/username + password credentials without multi-factor authentication (MFA/TOTP).
- Sample Password Blocklist: The current password blocklist in
app/schemas/auth.pycontains a representative sample for demonstration; production deployments should validate chosen credentials against full breached-password datasets (such as Have I Been Pwned via k-Anonymity) or an offline dictionary.
- Distributed Rate Limiting: Migrate rate limiter backend to Redis using Lua scripts for atomic sliding window evaluation across clustered instances.
- Token Revocation & Refresh Flow: Introduce short-lived access tokens (15 mins) paired with rotating refresh tokens stored in Redis with revocation blacklists.
- Breached Password Verification: Integrate Have I Been Pwned (HIBP) Pwned Passwords API using k-Anonymity SHA-1 hash ranges during registration.
- TOTP Two-Factor Authentication: Implement RFC 6238 time-based one-time passwords for enhanced login security.
- Role-Based Access Control (RBAC): Introduce granular scopes (
notes:read,notes:write,admin) to demonstrate Broken Function Level Authorization (BFLA) defenses. - Database Migration Pipeline: Integrate Alembic for formal versioned database schema migrations.
This project is licensed under the MIT License - see the LICENSE file for details.