Skip to content

About

Hardened REST API demonstrating defensive engineering: Argon2id, JWT, Anti-IDOR, rate limiting, and OWASP API security.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

secure-api: Defensive REST API & Notes Service

CI License: MIT Python: 3.12+ Security: Bandit

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.


Table of Contents


What It Does

secure-api provides a multi-tenant notes management service where every operation is guarded by defensive security controls:

  1. 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).
  2. 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.
  3. 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.

Architecture & Defensive Controls

               [ 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)
             └───────────────────────────┘

Security Implementations

Password Policy & Blocklist (NIST SP 800-63B)

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:

  1. Enforcing adequate minimum length (at least 8 characters).
  2. 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.

Client IP & Anti-Spoofing Rate Limiting

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.

JWT Secret Key & Startup Enforcement

The application reads its signing secret from the SECRET_KEY environment variable. To prevent key leaks:

  1. Pydantic validates that the key has at least 32 characters of cryptographic entropy.
  2. In non-production environments, if the default development key is detected, the application logs a prominent security warning banner.
  3. If ENVIRONMENT=production, the application refuses to start and raises a fatal exception if the default insecure key is still configured.

Anti-IDOR Authorization

To prevent Broken Object Level Authorization (OWASP API1:2023):

  1. Note ownership is derived strictly from the verified JWT sub claim, not caller-supplied request body fields.
  2. All database queries combine object ID and owner ID: db.query(Note).filter(Note.id == note_id, Note.owner_id == current_user.id).
  3. Unauthorized cross-tenant queries return HTTP 404 Not Found rather than HTTP 403 Forbidden to neutralize resource existence enumeration.

Installation & Setup

Prerequisites

  • Python 3.12+
  • Git
  • Optional: uv for fast dependency management

1. Clone & Navigate

git clone https://github.com/ErenYeager1836/secure-api.git
cd secure-api

2. Create Virtual Environment

Using standard Python venv:

python -m venv .venv

# On Linux / macOS:
source .venv/bin/activate

# On Windows (PowerShell):
.\.venv\Scripts\Activate.ps1

Or using uv:

uv venv .venv
.\.venv\Scripts\activate

3. Install Dependencies

pip install -r requirements.txt

4. Configure Environment

Copy the example environment configuration:

cp .env.example .env

In local development, the default values in .env.example allow testing immediately. For production, generate a strong random secret with python -c "import secrets; print(secrets.token_hex(32))".


Running the Application

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 --reload

The server will initialize SQLite database tables automatically on startup.


Usage Guide & Demo Output

The following outputs are verbatim responses captured from a live test instance:

1. Register a User

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"
}

2. Authenticate & Obtain JWT

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
}

3. Create a Note

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"
}

4. IDOR Demonstration: Cross-Tenant Access Blocked

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 Found rather than 403 Forbidden prevents attackers from probing and confirming the existence of victim notes.

5. Brute Force Defense: Rate Limiting

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


Running Tests & Linting

Run Full Test Suite (pytest)

pytest -v

Output:

======================== 28 passed, 1 warning in 7.79s ========================

Run Static Application Security Testing (Bandit)

Scans Python AST for security vulnerabilities (injection, weak crypto, unsafe deserialization):

bandit -r app -ll

Output:

Test results:
    No issues identified.
Code scanned:
    Total lines of code: 1102
Run metrics:
    Total issues (by severity): Medium: 0, High: 0

Run Code Style & Linter (flake8)

flake8 app tests --max-line-length=120

Docker Deployment

Build 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"

Threat Model

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.


Limitations & Roadmap

In adherence to clean scope engineering, this initial version prioritizes robust core security primitives. The following enhancements are tracked on the roadmap:

Current Limitations

  1. 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.
  2. Stateless JWT Revocation: Tokens remain valid until their expiration timestamp (exp). There is currently no server-side token revocation or blocklist.
  3. Single Factor Authentication: Supports email/username + password credentials without multi-factor authentication (MFA/TOTP).
  4. Sample Password Blocklist: The current password blocklist in app/schemas/auth.py contains 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.

Roadmap

  • 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.

License

This project is licensed under the MIT License - see the LICENSE file for details.

About

Hardened REST API demonstrating defensive engineering: Argon2id, JWT, Anti-IDOR, rate limiting, and OWASP API security.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages