A production-inspired FastAPI Employee CRUD API hardened with layered security controls including API key authentication, JWT authentication, rate limiting, CORS, secure headers, input validation, SQL injection prevention, XSS input protection, environment-based configuration, and HTTPS-ready deployment support.
- ๐ Overview
- ๐ฏ Objective
- ๐๏ธ Architecture
- ๐ Security Architecture
- ๐ ๏ธ Technology Stack
- ๐ Project Structure
- โจ Security Features
- โ๏ธ Setup & Installation
- ๐ Environment Variables
- ๐ Running the Application
- ๐ API Endpoints
- ๐งช Security Testing
- ๐ฆ Rate Limiting Demo
- ๐ CORS Demo
- ๐ก๏ธ Security Headers
- ๐ HTTPS-Ready Configuration
- ๐ฅ YouTube Demonstration
- ๐งฉ Troubleshooting
- ๐ Assignment Coverage
- ๐ฎ Future Improvements
- ๐ง Key Engineering Lessons
- ๐จโ๐ป Author
The API Security Audit project starts with a small Employee CRUD REST API and progressively hardens it against common API security risks.
The project demonstrates an important engineering workflow:
flowchart LR
A[Basic FastAPI CRUD] --> B[Security Audit]
B --> C[Authentication]
B --> D[Input Protection]
B --> E[HTTP Security]
B --> F[Abuse Protection]
C --> G[Hardened API]
D --> G
E --> G
F --> G
The goal is not simply to add security libraries. Each security control is implemented and tested against a concrete requirement.
- API Key Authentication
- JWT Authentication
- HTTPS-ready configuration
- Rate Limiting
- CORS
- Secure HTTP Headers
- Input Validation
- SQL Injection Prevention
- XSS Prevention
- Environment Variables
- Secret Management
- Redis-backed Rate Limiting
The security audit includes before-and-after attack demonstrations showing how requests behave before and after the corresponding security controls are applied.
flowchart TD
Client[Client / Browser / Postman]
Proxy[Nginx / HTTPS Reverse Proxy]
Rate[Rate Limiting Middleware]
CORS[CORS Middleware]
Headers[Security Headers]
Router[FastAPI Router]
APIKey[API Key Validation]
JWT[JWT Validation]
Schema[Pydantic Validation]
ORM[SQLAlchemy ORM]
DB[(SQLite Database)]
Client --> Proxy
Proxy --> Rate
Rate --> CORS
CORS --> Headers
Headers --> Router
Router --> APIKey
APIKey --> JWT
JWT --> Schema
Schema --> ORM
ORM --> DB
Client Request
โ
โผ
Rate Limiting
โ
โผ
CORS / HTTP Security
โ
โผ
API Key Authentication
โ
โผ
JWT Authentication
โ
โผ
Pydantic Input Validation
โ
โผ
SQLAlchemy ORM
โ
โผ
Database
Protected employee endpoints currently require both:
X-API-Key
+
Authorization: Bearer <JWT>
โ
Protected Endpoint
The API key identifies an approved client/application.
The JWT represents an authenticated user session.
This project intentionally demonstrates both mechanisms so their roles can be understood separately.
| Layer | Technology | Purpose |
|---|---|---|
| API Framework | FastAPI | REST API and request handling |
| Language | Python 3.13+ | Application logic |
| ORM | SQLAlchemy | Database access using parameterized queries |
| Database | SQLite | Local employee persistence |
| Validation | Pydantic | Request and response validation |
| Authentication | API Key + JWT | Client and user authentication |
| JWT Library | python-jose |
JWT creation and verification |
| Configuration | python-dotenv |
Environment-based configuration |
| Middleware | Starlette/FastAPI | CORS, headers, rate limiting, HTTPS |
| Development Server | Uvicorn | ASGI server |
api-security-audit/
โ
โโโ app/
โ โโโ __init__.py
โ โโโ main.py # FastAPI application and middleware
โ โโโ database.py # SQLAlchemy engine/session
โ โโโ models.py # Database models
โ โโโ schemas.py # Pydantic request/response schemas
โ โ
โ โโโ routers/
โ โ โโโ __init__.py
โ โ โโโ employees.py # Employee CRUD endpoints
โ โ โโโ auth.py # Login endpoint
โ โ
โ โโโ security/
โ โ โโโ __init__.py
โ โ โโโ api_key.py # API key verification
โ โ โโโ jwt.py # JWT creation/verification
โ โ โโโ jwt_auth.py # JWT authentication dependency
โ โ โโโ headers.py # Security response headers
โ โ
โ โโโ middleware/
โ โโโ __init__.py
โ โโโ rate_limit.py # IP-based rate limiting
โ
โโโ tests/
โ โโโ __init__.py
โ
โโโ .env.example # Safe configuration template
โโโ .gitignore # Git ignore rules
โโโ README.md
โโโ requirements.txt
.envis intentionally excluded from source control.
Protected employee endpoints require an X-API-Key header.
X-API-Key: <your-api-key>Missing or incorrect keys return:
401 UnauthorizedThe implementation uses secrets.compare_digest() for the key comparison.
Users authenticate through:
POST /auth/loginA successful login returns a bearer token.
Protected requests use:
Authorization: Bearer <JWT>JWT expiration is configured through an environment variable.
The application allows:
10 requests / 60 seconds / client IP
Exceeding the limit returns:
429 Too Many RequestsSwagger and OpenAPI documentation paths are excluded from the rate limiter so documentation browsing does not consume the API quota.
The current implementation is in-memory and intended for the project/demo environment. Redis-backed rate limiting is a future/bonus improvement for distributed deployments.
Allowed browser origins are configured through:
ALLOWED_ORIGINS=http://localhost:3000Unauthorized browser origins receive a CORS rejection.
The API adds:
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
Referrer-Policy: no-referrer
Content-Security-Policy: default-src 'none'CSP is skipped for Swagger/OpenAPI documentation paths because Swagger requires browser-loaded assets to render correctly.
Pydantic validates:
- Name length
- Email format
- Department length
- Non-negative salary
- Positive pagination values
- Required response identifiers
Invalid requests return 422 Unprocessable Entity.
Database operations use SQLAlchemy ORM queries instead of dynamically concatenating SQL strings.
User input is therefore treated as data rather than being inserted directly into SQL statements.
Employee name and department fields reject HTML-like tags.
Example:
<script>alert("XSS")</script>is rejected by request validation.
XSS protection is context-dependent. If a future frontend renders user-controlled data as HTML, that frontend must also safely escape or sanitize output.
Sensitive values are stored in .env rather than committed to Git.
Examples include:
- API key
- JWT secret
- Authentication password
- Database URL
.env.example contains only placeholder values.
Uvicorn is configured to understand forwarded proxy headers:
uvicorn app.main:app --reload --proxy-headersProduction deployments can place FastAPI behind an HTTPS reverse proxy such as Nginx.
HTTPS enforcement is environment-controlled:
FORCE_HTTPS=falseSet it to true when HTTPS termination is configured in the deployment environment.
git clone <YOUR_GITHUB_REPOSITORY_URL>
cd api-security-auditpython3.13 -m venv venv
source venv/bin/activatepip install --upgrade pip
pip install -r requirements.txtcp .env.example .envEdit .env and provide your own local secrets.
Never copy real secrets into
.env.example.
Example configuration:
# Database
DATABASE_URL=sqlite:///./employee_db.sqlite
# API Authentication
API_KEY=your-secure-api-key
# JWT
JWT_SECRET_KEY=your-long-random-secret
JWT_ALGORITHM=HS256
JWT_EXPIRE_MINUTES=30
# Login
AUTH_USERNAME=admin
AUTH_PASSWORD=your-local-password
# CORS
ALLOWED_ORIGINS=http://localhost:3000
# HTTPS
FORCE_HTTPS=falseGenerate strong random secrets with:
python -c "import secrets; print(secrets.token_hex(32))"Verify that .env is ignored:
git check-ignore -v .envExpected output should reference .gitignore.
Start the API with proxy-header support:
uvicorn app.main:app --reload --proxy-headershttp://127.0.0.1:8000/docs
http://127.0.0.1:8000/redoc
| Method | Endpoint | Purpose | Auth |
|---|---|---|---|
| POST | /auth/login |
Generate JWT | None |
| Method | Endpoint | Purpose |
|---|---|---|
| POST | /employees/ |
Create employee |
| GET | /employees/ |
List employees |
| GET | /employees/{id} |
Get employee |
| PUT | /employees/{id} |
Update employee |
| PATCH | /employees/{id} |
Partial update |
| DELETE | /employees/{id} |
Delete employee |
Employee endpoints require:
X-API-Key: <API_KEY>
Authorization: Bearer <JWT>The project demonstrates security behavior using requests before and after controls are implemented.
Expected:
401 UnauthorizedExpected:
401 UnauthorizedExpected:
401 UnauthorizedExample:
{
"name": "",
"email": "not-an-email",
"salary": -100
}Expected:
422 Unprocessable EntityExample:
{
"name": "<script>alert('XSS')</script>",
"email": "[email protected]"
}Expected:
422 Unprocessable EntityExample:
' OR '1'='1
The SQLAlchemy ORM treats the value as data rather than dynamically executing it as SQL.
After the configured request limit is exceeded:
429 Too Many RequestsRestart the application first so the in-memory counter starts fresh.
Then send 11 authenticated requests:
for i in {1..11}; do
curl -s -o /dev/null -w "Request $i: %{http_code}\n" "http://127.0.0.1:8000/employees/?page=1&size=20" -H "X-API-Key: YOUR_API_KEY" -H "Authorization: Bearer YOUR_JWT"
doneExpected:
Request 1: 200
Request 2: 200
...
Request 10: 200
Request 11: 429
curl -i -X OPTIONS http://127.0.0.1:8000/employees/ -H "Origin: http://localhost:3000" -H "Access-Control-Request-Method: GET"Expected response includes:
access-control-allow-origin: http://localhost:3000curl -i -X OPTIONS http://127.0.0.1:8000/employees/ -H "Origin: http://evil-example.com" -H "Access-Control-Request-Method: GET"The request should be rejected by the CORS configuration.
Check the API response headers:
curl -i http://127.0.0.1:8000/Expected security headers include:
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
Referrer-Policy: no-referrer
Content-Security-Policy: default-src 'none'Swagger/OpenAPI documentation paths intentionally have different CSP handling so the interactive documentation can load correctly.
The application supports deployment behind an HTTPS reverse proxy.
flowchart LR
Browser[Browser]
HTTPS[HTTPS / TLS]
Nginx[Nginx Reverse Proxy]
Uvicorn[Uvicorn]
FastAPI[FastAPI]
Browser --> HTTPS
HTTPS --> Nginx
Nginx --> Uvicorn
Uvicorn --> FastAPI
Run the application with:
uvicorn app.main:app --reload --proxy-headersFor an HTTPS-enabled deployment:
FORCE_HTTPS=trueLocal development should normally remain:
FORCE_HTTPS=falsebecause the local development server does not provide a TLS certificate.
Recommended demonstration sequence:
Demonstrate representative attacks or missing controls against the original CRUD API:
- Unauthenticated employee access
- Invalid input
- XSS-style input
- SQL injection-style input
- Repeated requests
Show:
API Key
โ
JWT
โ
Validation
โ
SQLAlchemy ORM
โ
Database
And the middleware layer:
Rate Limiting
CORS
Security Headers
HTTPS-ready proxy handling
Show:
- Missing API key โ
401 - Invalid JWT โ
401 - Invalid input โ
422 - XSS payload โ rejected
- CORS violation โ rejected
- Rate limit exceeded โ
429 - Security headers โ present
- Valid authenticated CRUD โ successful
Protected employee endpoints require:
X-API-Key: YOUR_API_KEYLogin again using:
POST /auth/login
Then send:
Authorization: Bearer YOUR_JWTCheck that the token has not expired.
Check that restrictive CSP is not applied to:
/docs
/redoc
/openapi.json
These documentation routes need browser assets to render correctly.
The current implementation uses in-memory counters. Restarting the development server resets the counters.
Swagger/OpenAPI documentation routes are excluded from the rate limiter.
Check:
git check-ignore -v .envMake sure .env is listed in .gitignore.
If it was accidentally staged:
git restore --staged .envNever publish an actual secret.
| Requirement | Implementation | Status |
|---|---|---|
| API Key Authentication | app/security/api_key.py |
โ |
| JWT | app/security/jwt.py + jwt_auth.py |
โ |
| HTTPS Ready | Proxy headers + configurable redirect | โ |
| Rate Limiting | app/middleware/rate_limit.py |
โ |
| CORS | CORSMiddleware |
โ |
| Secure Headers | app/security/headers.py |
โ |
| Input Validation | app/schemas.py |
โ |
| SQL Injection Prevention | SQLAlchemy ORM | โ |
| XSS Prevention | HTML-like input rejection | โ |
| Environment Variables | .env + python-dotenv |
โ |
| Secret Management | .gitignore + .env.example |
โ |
| Redis Rate Limiting | Not yet implemented | โณ |
- Authentication is layered.
- Validation belongs close to the API boundary.
- ORM parameterization helps prevent SQL injection.
- Security headers protect browser-facing behavior.
- CORS controls browser origins, not general API access.
- Rate limiting protects APIs from excessive requests.
- Secrets should live outside source code.
- HTTPS should be part of the deployment architecture.
- Security controls should be tested, not only configured.
- Local development and production configuration should be separated.
- Redis-backed distributed rate limiting
- PostgreSQL for production database
- Automated security test suite
- Password hashing with a dedicated password-hashing algorithm
- Refresh-token flow
- Role-based access control
- Structured logging and audit trails
- Nginx HTTPS deployment with real TLS certificates
- Secret manager integration
- CI/CD security checks
- Dependency vulnerability scanning
Vijay Rangvani
Backend & AI Engineering Learner | Healthcare IT
Python ยท FastAPI ยท REST APIs ยท SQLAlchemy ยท JWT ยท API Security ยท Docker ยท Cloud Systems
Built as a practical API security audit project demonstrating secure backend engineering patterns through implementation and attack testing.