Secure Auth API - Zero-Dependency Authentication from Scratch
Why I Built This
Most developers reach for Flask, Django, or Express when they need auth. That’s fine for production apps, but it means the actual security logic is buried under framework abstractions. I wanted to understand what’s really happening when a user logs in, how tokens get signed, and where the attack surface lives.
So I built an entire authentication and authorization API from scratch using nothing but Python’s standard library. No Flask. No Django. No pip installs. Just the http.server, hashlib, hmac, and sqlite3 modules that ship with Python.
What It Does
This is a full OAuth2-pattern REST API that handles the complete auth lifecycle:
- User registration with password complexity enforcement
- Login that returns JWT access tokens (15 min) and rotating refresh tokens (7 days)
- Role-Based Access Control with three tiers: Viewer, Editor, Admin
- Brute-force protection that locks accounts after 5 failed attempts
- Rate limiting with a sliding window (30 requests per minute per IP)
- Audit logging for every auth event
Every endpoint follows REST conventions and returns proper HTTP status codes with JSON responses.
Security Decisions That Matter
Password Storage
Passwords are hashed with PBKDF2-SHA256 using 100,000 iterations and a random 32-byte salt per user. The iteration count is intentionally high to make offline cracking expensive. No bcrypt dependency needed because PBKDF2 ships with Python’s hashlib.
JWT Implementation
Access tokens are signed with HMAC-SHA256. I built the JWT encoding and decoding manually to understand the three-part structure (header.payload.signature) and to avoid pulling in PyJWT as a dependency. Token verification uses hmac.compare_digest for constant-time comparison, which prevents timing attacks where an attacker measures response times to guess valid signatures byte by byte.
Token Rotation
When a client uses a refresh token to get a new access token, the old refresh token is revoked and a new one is issued. This means a stolen refresh token can only be used once before it’s invalidated. If an attacker and the legitimate user both try to use the same refresh token, the second attempt fails and the account can be flagged.
Generic Error Messages
The login endpoint returns the same error message whether the username doesn’t exist or the password is wrong. This prevents user enumeration, where an attacker probes the login form to build a list of valid usernames.
Self-Protection
Admins can’t change their own role or deactivate their own account through the API. This prevents a compromised admin session from locking out the account owner.
Demo Output
1
2
3
4
5
6
7
8
9
10
Register: 201
Login: 200
Profile: 200 - role=admin, perms=['delete', 'manage_users', 'read', 'write']
Register user: 201
List users: 200 - 2 users
Brute force: LOCKED after 6 attempts
Health: 200
Audit log: 200 - 9 events
All tests passed
Endpoints
| Method | Path | Auth | What It Does |
|---|---|---|---|
| POST | /api/register |
None | Create account |
| POST | /api/login |
None | Get access + refresh tokens |
| POST | /api/refresh |
None | Rotate tokens |
| POST | /api/logout |
Bearer | Revoke session |
| GET | /api/me |
Bearer | Current user profile |
| GET | /api/users |
Admin | List all users |
| PUT | /api/users/:id/role |
Admin | Change user role |
| DELETE | /api/users/:id |
Admin | Deactivate user |
| GET | /api/audit |
Admin | View audit log |
| GET | /health |
None | Health check |
Tech Stack
- Python 3.10+ (standard library only)
- SQLite with WAL mode
- PBKDF2-SHA256 (100k iterations)
- HMAC-SHA256 JWT signing
- Custom HTTP request handler
What I Learned
Building auth from scratch forces you to think about every edge case that frameworks handle silently. Things like: What happens if two requests try to refresh the same token simultaneously? (Token rotation handles it.) What if someone sends a malformed JWT with a modified algorithm header? (Reject anything that isn’t HS256.) How do you prevent an admin API from being used to escalate privileges? (Block self-modification.)
This is the kind of project where the security decisions are the feature set. Every line of code is a tradeoff between usability and defense.