lua-resty-digest-auth
A modern, production-ready OpenResty module for HTTP Digest Authentication with advanced security features
$ opm get ElCruncharino/lua-resty-digest-auth
lua-resty-digest-auth
[!Build Status](https://github.com/ElCruncharino/lua-resty-digest-auth/actions) [!License](https://opensource.org/licenses/MIT) [!OpenResty](https://openresty.org/) [!OPM](https://opm.openresty.org/) [!Codacy Badge](https://app.codacy.com/gh/ElCruncharino/lua-resty-digest-auth/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade)
An OpenResty module implementing RFC 2617 HTTP Digest Authentication with brute force protection, rate limiting, and suspicious pattern detection.
Features
RFC 2617 compliant HTTP Digest Authentication
Brute force protection with configurable thresholds
Rate limiting per client IP
Suspicious pattern detection (empty credentials, malformed headers, rapid requests)
Username enumeration protection
Optimized nonce management with configurable reuse limits
Shared memory support across Nginx workers
Simple configuration with sensible defaults
Installation
Via OPM
opm get ElCruncharino/lua-resty-digest-auth
Manual Installation
git clone https://github.com/ElCruncharino/lua-resty-digest-auth.git
cd lua-resty-digest-auth
make install
Docker Testing
cd test
docker-compose up --build
curl -u alice:password123 http://localhost:8080/protected/
Quick Start
1. Create User Credentials
Create a file (e.g., htdigest) with user credentials:
username:realm:HA1_hash
Generate HA1 hash: echo -n "username:realm:password" | md5sum
2. Configure Nginx
# Define shared memory
lua_shared_dict digest_auth 2m;
lua_shared_dict digest_auth_ratelimit 1m;
# Initialize the module
init_by_lua_block {
local DigestAuth = require "resty.digest_auth"
local ok, err = DigestAuth.configure {
shared_memory_name = "digest_auth",
credentials_file = "/path/to/htdigest",
realm = "My Protected Area",
brute_force = {
enabled = true,
max_failed_attempts = 5,
window_seconds = 300,
block_seconds = 1800
}
}
if not ok then
error("Failed to configure digest auth: " .. (err or "unknown error"))
end
}
server {
listen 80;
server_name example.com;
# Protected endpoint
location /protected/ {
access_by_lua_block {
local DigestAuth = require "resty.digest_auth"
DigestAuth.require_auth()
}
return 200 "Protected content!";
}
}
Configuration
Basic Configuration
DigestAuth.configure {
shared_memory_name = "digest_auth", -- Required: shared memory name
credentials_file = "/path/to/htdigest", -- Required: user credentials file
realm = "Protected Area", -- Optional: authentication realm
nonce_lifetime = 600, -- Optional: nonce validity (seconds)
max_nonce_uses = 500, -- Optional: max nonce reuses
refresh_threshold = 80, -- Optional: refresh at % usage
}
Advanced Security Configuration
DigestAuth.configure {
shared_memory_name = "digest_auth",
credentials_file = "/path/to/htdigest",
realm = "Secure Area",
-- Rate limiting
rate_limit = {
enabled = true,
max_attempts = 10,
window_seconds = 300,
block_seconds = 60
},
-- Brute force protection
brute_force = {
enabled = true,
max_failed_attempts = 5,
window_seconds = 300,
block_seconds = 1800,
suspicious_patterns = {
common_passwords = {"password", "123456", "admin", "root", "test"},
empty_credentials = true,
malformed_headers = true,
rapid_requests = 5,
username_enumeration = 3
}
}
}
API Reference
DigestAuth.configure(options)
Configures the module. Must be called before using authentication.
Parameters:
options(table): Configuration options
Returns:
ok(boolean):trueon success,falseon failureerr(string): Error message if failed
DigestAuth.require_auth()
Requires authentication for the current request. Exits with appropriate HTTP status codes.
Returns:
Exits with
ngx.OK(200) if authenticatedExits with
ngx.HTTP_UNAUTHORIZED(401) if authentication requiredExits with
ngx.HTTP_FORBIDDEN(403) if rate limited or blockedExits with
ngx.HTTP_BAD_REQUEST(400) if malformed request
DigestAuth.clear_memory()
Clears all shared memory used by the module.
DigestAuth.clear_nonces()
Clears only nonce-related entries from shared memory.
DigestAuth.cleanup_expired_nonces()
Cleans up expired nonce entries. Call this periodically (e.g., via a timer) to prevent memory exhaustion in high-traffic environments.
Security Features
Brute Force Protection
Tracks failed authentication attempts per client IP
Blocks clients after configurable failed attempts
Detects suspicious patterns (empty credentials, malformed headers, rapid requests)
Prevents username enumeration attacks
Configurable blocking durations and thresholds
Rate Limiting
Per-client IP tracking
Configurable time windows and attempt limits
Automatic recovery after timeout period
Monitoring & Logging
Enhanced logging with separate auth and security formats
Security event tracking
Health check endpoints
Examples
Basic Protection
location /api/ {
access_by_lua_block {
local DigestAuth = require "resty.digest_auth"
DigestAuth.require_auth()
}
# Your API content
}
Multiple Realms
init_by_lua_block {
local DigestAuth = require "resty.digest_auth"
local ok, err = DigestAuth.configure {
shared_memory_name = "admin_auth",
credentials_file = "/etc/nginx/admin_users",
realm = "Admin Area",
brute_force = { enabled = true, max_failed_attempts = 3 }
}
if not ok then error("Admin auth setup failed: " .. err) end
}
location /admin/ {
access_by_lua_block {
local DigestAuth = require "resty.digest_auth"
DigestAuth.require_auth()
}
}
Production Configuration
# Enhanced logging
log_format auth '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for" '
'auth_user="$http_authorization"';
log_format security '$remote_addr - [$time_local] "$request" '
'$status "$http_user_agent" '
'event="$sent_http_x_security_event" '
'reason="$sent_http_x_block_reason"';
access_log /var/log/nginx/auth.log auth;
access_log /var/log/nginx/security.log security;
# Shared memory
lua_shared_dict digest_auth 4m;
lua_shared_dict digest_auth_ratelimit 2m;
init_by_lua_block {
local DigestAuth = require "resty.digest_auth"
local ok, err = DigestAuth.configure {
shared_memory_name = "digest_auth",
credentials_file = "/etc/nginx/htdigest",
realm = "Secure Area",
nonce_lifetime = 300,
max_nonce_uses = 100,
rate_limit = {
enabled = true,
max_attempts = 10,
window_seconds = 300,
block_seconds = 60
},
brute_force = {
enabled = true,
max_failed_attempts = 5,
window_seconds = 300,
block_seconds = 1800,
suspicious_patterns = {
common_passwords = {"password", "123456", "admin", "root", "test"},
empty_credentials = true,
malformed_headers = true,
rapid_requests = 5,
username_enumeration = 3
}
}
}
if not ok then
error("Failed to configure digest auth: " .. (err or "unknown error"))
end
}
Testing
The module includes several test scripts:
Docker Testing (Recommended)
cd test
docker-compose up --build
curl --digest -u alice:password123 http://localhost:8080/protected/
Native Linux Testing
cd test
chmod +x setup_linux.sh
./setup_linux.sh
start_test_server
test_digest_auth
User Credentials File
The credentials file should contain one entry per line in the format:
username:realm:HA1_hash
Generating Credentials
Using OpenSSL:
echo -n "username:realm:password" | openssl md5
Using MD5sum:
echo -n "username:realm:password" | md5sum
Using Python:
import hashlib
print(hashlib.md5("username:realm:password".encode()).hexdigest())
Example:
# For user "alice" with password "password123" in realm "Test Realm"
echo -n "alice:Test Realm:password123" | md5sum
# Result: 49e8d18599d46ed533eb6f4ca0325170
Troubleshooting
Common Issues
"shared_memory_name not found": Ensure shared memory is defined in nginx.conf
"credentials_file not found": Check file path and permissions
"No valid users found": Verify credentials file format
Authentication failures: Check realm matches between config and credentials file
Debug Logging
Enable debug logging:
error_log /var/log/nginx/error.log debug;
Memory Issues
Increase shared memory allocation:
lua_shared_dict digest_auth 8m; # Increase from 2m to 8m
Monitoring
Health Checks
# Check module status
curl -f http://your-domain.com/health
# Check authentication
curl --digest -u username:password http://your-domain.com/protected/
Log Analysis
# Failed authentication attempts
grep "403\|401" /var/log/nginx/auth.log
# Brute force attempts
grep "brute_force" /var/log/nginx/error.log
# Rate limiting events
grep "rate_limit" /var/log/nginx/error.log
Contributing
Pull requests are welcome. Please include tests for any new features or bug fixes.
License
This project is licensed under the MIT License - see the LICENSE file for details.
Support
Issues: GitHub Issues
- Documentation: Test Documentation
Security Hardening
This module includes additional security mitigations beyond standard Digest Auth:
Header name and value sanitization and length checks
Shared memory key sanitization
Log value sanitization
Credential file path validation (absolute paths, no path traversal)
Constant-time string comparison for authentication
Utility for periodic nonce cleanup
These mitigations reduce the risk of memory exhaustion, log injection, timing attacks, and key collisions.
Authors
ElCruncharino
License
mit
Versions
-
A modern, production-ready OpenResty module for HTTP Digest Authentication with advanced security features 2025-11-18 22:53:26
-
A modern, production-ready OpenResty module for HTTP Digest Authentication with advanced security features 2025-07-02 02:00:24
-
A modern, production-ready OpenResty module for HTTP Digest Authentication with advanced security features 2025-06-30 01:40:46
-
A modern, production-ready OpenResty module for HTTP Digest Authentication with advanced security features 2025-06-30 00:01:51