A secure HTTP server implementation using libmicrohttpd, SQLite3, and libsodium that provides robust user authentication with session management and configurable rate limiting to prevent abuse.
Docker Support: A Dockerfile is provided for easy cross-platform deployment. If you don't have the required libraries installed or use a different OS, you can run this application using Docker. See the Docker Deployment section below.
# Clone the repository
git clone https://github.com/MohamedMouloudj/LL-Rate-Limiter
cd LL-Rate-Limiter
# Start with docker-compose
docker-compose up -d
# Access the server
open http://localhost:8080Requires: libmicrohttpd, libsqlite3, libsodium, build-essential
make
./serverBefore building, ensure the following libraries are installed:
sudo apt-get update
sudo apt-get install libmicrohttpd-dev libsqlite3-dev libsodium-dev build-essential- libmicrohttpd - Lightweight HTTP server library (required)
- libsqlite3 - Embedded SQL database engine (required)
- libsodium - Modern cryptographic library for password hashing with Argon2id (required)
- build-essential - GCC compiler and build tools (required)
Ensure you have equivalent packages:
- GCC compiler
- libmicrohttpd (HTTP server library)
- SQLite3 (database)
- libsodium (cryptographic functions - Argon2id password hashing)
The system is organized into modular components with clear separation of concerns:
┌─────────────────────────────────────────────────────────────┐
│ HTTP Server │
│ (libmicrohttpd - Port 8080) │
└────────────────┬────────────────────────────────────────────┘
│
├──► Rate Limiter ──► Database (rate_limit table)
│ - Checks IP-based request limits (form submissions only)
│ - Enforces temporary blocks
│
├──► Session Manager ──► Database (sessions table)
│ - Creates secure random session IDs
│ - Validates sessions with IP binding
│ - Handles session expiration
│
├──► Authentication Module
│ - Argon2id password hashing (libsodium)
│ - Input validation
│ │
│ └──► Database (users table)
│
└──► HTML Page Generator
- Register, Login, Dashboard, Error pages
-
HTTP Server (
http_server.c): Core request handler using libmicrohttpd with thread-per-connection model- Routes incoming requests to appropriate handlers
- Extracts client IP addresses for rate limiting
- Manages POST data processing
- Handles session state (simplified via URL parameters)
-
Rate Limiter (
rate_limiter.c): IP-based request tracking with configurable algorithms- Checks request count against configured limits for form submissions only
- Blocks IPs that exceed thresholds
- Supports both sliding and fixed window algorithms
-
Session Manager (
session.c): Secure session lifecycle management- Cryptographically secure random session ID generation (32 bytes → 64 hex chars)
- IP address binding to prevent session hijacking
- Automatic session expiration (30-minute inactivity, 24-hour absolute limit)
- Periodic cleanup of expired sessions
-
Authentication (
auth.c): Secure user registration and login- Argon2id password hashing via libsodium with automatic salt generation
- Input validation (username format, password length)
- Credential verification with constant-time comparison
-
Database (
database.c): SQLite3 persistence layer- User account storage with Argon2id hashed passwords
- Session storage with expiration tracking
- Rate limit tracking per IP address
- Automatic cleanup of expired entries
-
Pages (
pages.c): Dynamic HTML generation- Consistent styling across all pages
- Error message display
- Form handling for registration/login
The rate limiter can be easily configured through constants in include/rate_limiter.h:
#define RATE_LIMIT_MAX_REQUESTS 10 // Maximum login/registration attempts
#define RATE_LIMIT_WINDOW_SECONDS 300 // Time window in seconds (5 minutes)Note: Rate limiting only applies to form submissions (POST to /login and /register), not to page views.
Example configurations:
- Strict:
MAX_REQUESTS=5, WINDOW=600(5 attempts per 10 minutes) - Moderate:
MAX_REQUESTS=10, WINDOW=300(10 attempts per 5 minutes - current) - Lenient:
MAX_REQUESTS=20, WINDOW=300(20 attempts per 5 minutes)
#define USE_SLIDING_WINDOW 1 // 1 = Sliding Window, 0 = Fixed WindowSliding Window Algorithm:
- Continuously tracks requests within a rolling time window
- More precise but slightly more complex
- Request count resets gradually as old requests fall outside the window
Fixed Window Algorithm:
- Resets counter at fixed intervals
- Simpler implementation
- Potential for burst traffic at window boundaries
After modifying these values, rebuild the project:
make rebuild| Route | Method | Description | Authentication Required |
|---|---|---|---|
/ |
GET | Redirects to login page | No |
/register |
GET | Display registration form | No |
/register |
POST | Process new user registration | No |
/login |
GET | Display login form | No |
/login |
POST | Authenticate user credentials | No |
/dashboard |
GET | Protected dashboard with user info | Yes (session cookie) |
/logout |
GET | End session and clear cookie | Yes (session cookie) |
/too-many-requests |
GET | Rate limit violation page | No |
-
Registration: User submits username and password (minimum 8 characters)
- Password is hashed with Argon2id (via libsodium) with automatic salt generation
- Stored securely in database (128 bytes)
- Redirected to login on success
-
Login: User submits credentials
- Password is verified using constant-time comparison against stored Argon2id hash
- On success: secure session created with cryptographically random 64-character session ID
- Session stored as HTTP-only cookie (not in URL)
- Cookie attributes:
HttpOnly; Path=/; Max-Age=86400; SameSite=Strict - Session bound to user's IP address to prevent hijacking
- Redirected to clean URL:
/dashboard(no session in URL) - On failure: error message displayed
-
Dashboard Access: Requires valid session cookie
- Browser automatically sends session cookie with each request
- Session validated: checks expiration (30-min inactivity / 24-hour absolute) and IP match
- Without valid session: redirected to login
- Shows personalized welcome message and demo content
- Session automatically renewed on each request (updates
last_activitytimestamp)
-
Logout: User can explicitly end session
- Access
/logoutto destroy session - Session removed from database
- Cookie cleared with
Max-Age=0 - Redirected to login page
- Access
All requests are checked against rate limits:
- If limit not exceeded: request proceeds normally
- If limit exceeded:
- IP is temporarily blocked for
RATE_LIMIT_WINDOW_SECONDS - Error message displayed on the form
- Block persists until window expires
- IP is temporarily blocked for
Important: Rate limiting only applies to form submissions (POST requests to /login and /register).
Page views (GET requests) are not rate limited, allowing users to browse freely.
The easiest way to run the application across all platforms:
# Start the server
docker-compose up -d
# View logs
docker-compose logs -f
# Stop the server
docker-compose down
# Stop and remove data
docker-compose down -vThe server will be available at http://localhost:8080
# Build the image
docker build -t rate-limiter .
# Run the container
docker run -d \
--name rate-limiter \
-p 8080:8080 \
-v ./data:/app/data \
rate-limiter
# View logs
docker logs -f rate-limiter
# Stop and remove
docker stop rate-limiter && docker rm rate-limiterCustomize rate limiting and server settings using environment variables:
docker run -d \
--name rate-limiter \
-p 8080:8080 \
-e RATE_LIMIT_MAX_REQUESTS=20 \
-e RATE_LIMIT_WINDOW_SECONDS=600 \
-e RATE_LIMIT_ALGORITHM=sliding \
-e HTTP_PORT=8080 \
-e DB_PATH=/app/data/ratelimiter.db \
-v ./data:/app/data \
rate-limiterOr update docker-compose.yml:
environment:
- RATE_LIMIT_MAX_REQUESTS=20
- RATE_LIMIT_WINDOW_SECONDS=600
- RATE_LIMIT_ALGORITHM=fixed # or "sliding"
- HTTP_PORT=8080
- DB_PATH=/app/data/ratelimiter.db| Variable | Default | Description |
|---|---|---|
RATE_LIMIT_MAX_REQUESTS |
10 |
Maximum login/registration attempts |
RATE_LIMIT_WINDOW_SECONDS |
300 |
Time window in seconds (5 minutes) |
RATE_LIMIT_ALGORITHM |
sliding |
Algorithm type: sliding or fixed |
HTTP_PORT |
8080 |
HTTP server port |
DB_PATH |
/app/data/ratelimiter.db |
SQLite database file path |
The SQLite database is stored in a Docker volume to persist across container restarts:
- docker-compose: Named volume
ll-rate-limiter-dataautomatically created - Manual run: Use
-v ./data:/app/datato mount local directory
- Base Image: Alpine Linux 3.19 (minimal size)
- Final Size: ~20-25 MB
- Security: Runs as non-root user (UID 1000)
- Architecture: Multi-stage build for optimization
If you prefer to build and run natively without Docker:
Install required libraries on Ubuntu/Debian:
sudo apt-get update
sudo apt-get install libmicrohttpd-dev libsqlite3-dev libsodium-dev build-essentialOn other systems, ensure you have:
- GCC compiler
- libmicrohttpd (HTTP server library)
- SQLite3 (database)
- libsodium (cryptographic library for Argon2id password hashing)
Build the project:
makeOther Makefile targets:
make clean # Remove build artifacts and database
make rebuild # Clean and rebuild from scratch
make run # Build and start the server
make help # Show all available targetsStart the server:
./serverOr use the Makefile:
make runThe server will:
- Initialize the SQLite database (
server.db) - Create necessary tables if they don't exist
- Start listening on port 8080
- Display configuration information
Access the server at: http://localhost:8080
Stop the server with Ctrl+C for graceful shutdown.
Stores registered user accounts:
CREATE TABLE users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT UNIQUE NOT NULL,
password_hash TEXT NOT NULL,
created_at INTEGER DEFAULT (strftime('%s', 'now'))
);Fields:
id: Unique user identifier (auto-incremented)username: Unique username (alphanumeric, underscore, hyphen allowed)password_hash: Argon2id hash of password (128 bytes with embedded salt)created_at: Unix timestamp of account creation
Stores active user sessions with security validation:
CREATE TABLE sessions (
session_id TEXT PRIMARY KEY,
username TEXT NOT NULL,
ip_address TEXT NOT NULL,
created_at INTEGER NOT NULL,
last_activity INTEGER NOT NULL,
expires_at INTEGER NOT NULL,
FOREIGN KEY (username) REFERENCES users(username) ON DELETE CASCADE
);
CREATE INDEX idx_username ON sessions(username);
CREATE INDEX idx_expires_at ON sessions(expires_at);Fields:
session_id: Cryptographically secure random 64-character hex string (primary key)username: Associated user accountip_address: Client IP bound to this session (prevents hijacking)created_at: Unix timestamp when session was createdlast_activity: Unix timestamp of last request (updated on each access)expires_at: Unix timestamp when session expires (24 hours from creation)
Indexes:
idx_username: Fast lookup of all sessions for a useridx_expires_at: Optimizes expired session cleanup
Session Lifecycle:
- Created on successful login with 32 random bytes (→ 64 hex chars)
- Validated on each dashboard access (checks expiration + IP match)
- Expires after 30 minutes of inactivity OR 24 hours absolute
- Destroyed on logout or automatic cleanup
Tracks request counts and blocks per IP address:
CREATE TABLE rate_limit (
ip_address TEXT PRIMARY KEY,
request_count INTEGER DEFAULT 0,
window_start INTEGER,
blocked_until INTEGER DEFAULT 0,
last_updated INTEGER
);
CREATE INDEX idx_blocked_until ON rate_limit(blocked_until);Fields:
ip_address: Client IP address (primary key)request_count: Number of requests in current windowwindow_start: Unix timestamp when current window beganblocked_until: Unix timestamp when block expires (0 if not blocked)last_updated: Unix timestamp of last request
Index: idx_blocked_until optimizes blocked IP lookups
- Automatic cleanup: Expired sessions and rate limit entries removed every 60 seconds
- UPSERT operations: Efficiently handle existing/new records
- Thread safety: SQLite handles concurrent access
- Persistent storage: Database survives server restarts
LL-Rate-Limiter/
├── Makefile # Build configuration
├── README.md # This file
├── include/ # Header files
│ ├── auth.h # Authentication function prototypes
│ ├── database.h # Database operation prototypes
│ ├── http_server.h # HTTP server prototypes
│ ├── pages.h # HTML generation prototypes
│ ├── rate_limiter.h # Rate limiter prototypes and config
│ └── session.h # Session management prototypes
├── src/ # Implementation files
│ ├── auth.c # Authentication logic (Argon2id hashing)
│ ├── database.c # SQLite operations
│ ├── http_server.c # HTTP request handling
│ ├── main.c # Entry point
│ ├── pages.c # HTML page generation
│ ├── rate_limiter.c # Rate limiting logic
│ └── session.c # Session creation/validation
├── obj/ # Object files (generated)
└── server.db # SQLite database (generated)
- Password Hashing: Argon2id via libsodium with automatic salt generation and memory-hard key derivation
- Cookie-Based Sessions: HTTP-only cookies prevent XSS attacks, sessions not exposed in URLs
- Session Security: Cryptographically secure random session IDs (32 bytes → 64 hex chars)
- Session Binding: IP address validation prevents session hijacking
- Session Expiration: 30-minute inactivity timeout, 24-hour absolute expiration
- CSRF Protection: SameSite=Strict cookie attribute prevents cross-site request forgery
- Input Validation: Username and password requirements enforced
- SQL Injection Prevention: Prepared statements for all queries
- Rate Limiting: Prevents brute force and DoS attacks on login/registration
- IP-based Blocking: Temporary bans for violators
- Constant-time Comparison: Password verification resistant to timing attacks
- Threading Model: Thread-per-connection for good portability
- Database: SQLite with indexed lookups for fast queries
- Memory Management: Proper cleanup prevents leaks
- Scalability: Suitable for moderate traffic loads
Current Limitations:
- No HTTPS support (plain HTTP only - cookies should use Secure flag with HTTPS)
- IPv4 only (no IPv6 support)
Port already in use:
# Find process using port 8080
sudo lsof -i :8080
# Kill the process
kill -9 <PID>Missing libraries:
# Check if libraries are installed
pkg-config --libs libmicrohttpd sqlite3 libsodium
# Reinstall if needed
sudo apt-get install --reinstall libmicrohttpd-dev libsqlite3-dev libsodium-devDatabase permission errors:
# Ensure database file is writable
chmod 644 server.dbCompilation errors:
# Verify GCC is installed
gcc --version
# Clean and rebuild
make clean && makeThis is a demonstration project for educational purposes.
Mohamed Mouloud Created as part of Data Security coursework (Semester 7, 2025) - with my exaggeration :P - .