Set up secure JWT-based authentication in OpenResty with OAuth2 provider integration using lua-resty-jwt module. Configure token validation, authentication middleware, and security policies for production web applications.
Prerequisites
- Root or sudo access
- Domain name with DNS configured
- OAuth2 provider (Google, Auth0, Keycloak, etc.)
- SSL certificates
- Basic understanding of JWT and OAuth2 flows
What this solves
OpenResty JWT authentication with OAuth2 integration provides secure, stateless authentication for modern web applications. This implementation eliminates the need for server-side session storage while ensuring secure token validation and OAuth2 provider integration. You'll configure lua-resty-jwt for token handling, implement authentication middleware, and establish security policies that protect your applications from unauthorized access.
Step-by-step configuration
Update system packages
Start by updating your package manager to ensure you get the latest versions of all dependencies.
sudo apt update && sudo apt upgrade -y
Install OpenResty and required dependencies
Install OpenResty web server with Lua scripting support and additional tools needed for JWT authentication.
sudo apt install -y wget gnupg ca-certificates
wget -qO - https://openresty.org/package/pubkey.gpg | sudo apt-key add -
echo "deb http://openresty.org/package/ubuntu $(lsb_release -sc) main" | sudo tee /etc/apt/sources.list.d/openresty.list
sudo apt update
sudo apt install -y openresty luarocks git build-essential libssl-dev
Install lua-resty-jwt module
Install the JWT library for OpenResty that handles token creation, parsing, and validation with cryptographic signatures.
sudo luarocks install lua-resty-jwt
sudo luarocks install lua-resty-http
sudo luarocks install lua-cjson
Create directory structure
Set up the necessary directories for OpenResty configuration, Lua modules, and SSL certificates with proper permissions.
sudo mkdir -p /etc/openresty/conf.d
sudo mkdir -p /etc/openresty/lua
sudo mkdir -p /etc/openresty/ssl
sudo mkdir -p /var/log/openresty
sudo chown -R openresty:openresty /etc/openresty /var/log/openresty
sudo chmod 755 /etc/openresty /var/log/openresty
sudo chmod 750 /etc/openresty/ssl
Generate JWT signing keys
Create RSA key pairs for JWT token signing and verification. The private key signs tokens while the public key validates them.
sudo openssl genrsa -out /etc/openresty/ssl/jwt_private.key 2048
sudo openssl rsa -in /etc/openresty/ssl/jwt_private.key -pubout -out /etc/openresty/ssl/jwt_public.key
sudo chown openresty:openresty /etc/openresty/ssl/jwt_*.key
sudo chmod 600 /etc/openresty/ssl/jwt_private.key
sudo chmod 644 /etc/openresty/ssl/jwt_public.key
Create JWT authentication module
Develop the core Lua module that handles JWT token validation, OAuth2 integration, and authentication logic.
local jwt = require "resty.jwt"
local http = require "resty.http"
local cjson = require "cjson"
local io = require "io"
local _M = {}
-- Configuration
_M.jwt_secret_key = nil
_M.oauth2_introspect_url = "https://oauth.example.com/oauth/introspect"
_M.oauth2_client_id = "your-client-id"
_M.oauth2_client_secret = "your-client-secret"
-- Load JWT public key
function _M.load_jwt_key()
local file = io.open("/etc/openresty/ssl/jwt_public.key", "r")
if not file then
ngx.log(ngx.ERR, "Could not open JWT public key file")
return nil
end
local key = file:read("*all")
file:close()
return key
end
-- Validate JWT token
function _M.validate_jwt(token)
if not _M.jwt_secret_key then
_M.jwt_secret_key = _M.load_jwt_key()
if not _M.jwt_secret_key then
return false, "JWT key not available"
end
end
local jwt_token = jwt:verify(_M.jwt_secret_key, token, {
alg = "RS256"
})
if not jwt_token.verified then
return false, "JWT verification failed"
end
-- Check expiration
if jwt_token.payload.exp and jwt_token.payload.exp < ngx.time() then
return false, "JWT token expired"
end
return true, jwt_token.payload
end
-- OAuth2 token introspection
function _M.introspect_oauth2_token(token)
local httpc = http.new()
httpc:set_timeout(5000)
local res, err = httpc:request_uri(_M.oauth2_introspect_url, {
method = "POST",
headers = {
["Content-Type"] = "application/x-www-form-urlencoded",
["Authorization"] = "Basic " .. ngx.encode_base64(_M.oauth2_client_id .. ":" .. _M.oauth2_client_secret)
},
body = "token=" .. token
})
if not res then
ngx.log(ngx.ERR, "OAuth2 introspection failed: ", err)
return false, "OAuth2 introspection error"
end
if res.status ~= 200 then
return false, "OAuth2 introspection failed with status: " .. res.status
end
local data = cjson.decode(res.body)
if not data.active then
return false, "OAuth2 token is not active"
end
return true, data
end
-- Extract token from request
function _M.extract_token()
local auth_header = ngx.var.http_authorization
if not auth_header then
return nil, "No authorization header"
end
local token = auth_header:match("Bearer%s+(.+)")
if not token then
return nil, "Invalid authorization header format"
end
return token
end
-- Main authentication function
function _M.authenticate()
local token, err = _M.extract_token()
if not token then
ngx.log(ngx.ERR, "Token extraction failed: ", err)
ngx.status = 401
ngx.header["Content-Type"] = "application/json"
ngx.say(cjson.encode({error = "unauthorized", message = err}))
ngx.exit(401)
end
-- Try JWT validation first
local valid, payload = _M.validate_jwt(token)
if valid then
-- Set user context
ngx.var.auth_user_id = payload.sub or payload.user_id
ngx.var.auth_user_email = payload.email
ngx.var.auth_user_roles = cjson.encode(payload.roles or {})
return
end
-- Fallback to OAuth2 introspection
valid, payload = _M.introspect_oauth2_token(token)
if valid then
-- Set user context from OAuth2 response
ngx.var.auth_user_id = payload.sub or payload.username
ngx.var.auth_user_email = payload.email
ngx.var.auth_user_roles = cjson.encode(payload.scope and {payload.scope} or {})
return
end
-- Authentication failed
ngx.log(ngx.ERR, "Authentication failed: ", payload)
ngx.status = 401
ngx.header["Content-Type"] = "application/json"
ngx.say(cjson.encode({error = "unauthorized", message = "Invalid or expired token"}))
ngx.exit(401)
end
-- Role-based access control
function _M.require_role(required_role)
local user_roles = ngx.var.auth_user_roles
if not user_roles then
ngx.status = 403
ngx.header["Content-Type"] = "application/json"
ngx.say(cjson.encode({error = "forbidden", message = "No roles assigned"}))
ngx.exit(403)
end
local roles = cjson.decode(user_roles)
for _, role in ipairs(roles) do
if role == required_role then
return
end
end
ngx.status = 403
ngx.header["Content-Type"] = "application/json"
ngx.say(cjson.encode({error = "forbidden", message = "Insufficient permissions"}))
ngx.exit(403)
end
return _M
Configure main OpenResty configuration
Set up the primary OpenResty configuration with Lua package paths, security headers, and authentication variables.
user openresty;
worker_processes auto;
error_log /var/log/openresty/error.log;
pid /var/run/openresty.pid;
events {
worker_connections 1024;
use epoll;
}
http {
include /etc/openresty/mime.types;
default_type application/octet-stream;
# Lua package path
lua_package_path "/etc/openresty/lua/?.lua;;";
lua_shared_dict jwt_cache 10m;
# Security headers
add_header X-Content-Type-Options nosniff always;
add_header X-Frame-Options DENY always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# Logging
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for" '
'auth_user="$auth_user_id"';
access_log /var/log/openresty/access.log main;
# Performance settings
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
# Gzip compression
gzip on;
gzip_vary on;
gzip_min_length 1000;
gzip_types text/plain application/json application/javascript text/css;
# Rate limiting
limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
limit_req_zone $binary_remote_addr zone=auth:10m rate=5r/s;
include /etc/openresty/conf.d/*.conf;
}
Create application server configuration
Configure a virtual server with JWT authentication, OAuth2 integration, and protected API endpoints with role-based access control.
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name example.com;
# SSL configuration
ssl_certificate /etc/ssl/certs/ssl-cert-snakeoil.pem;
ssl_certificate_key /etc/ssl/private/ssl-cert-snakeoil.key;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;
# Authentication variables
set $auth_user_id "";
set $auth_user_email "";
set $auth_user_roles "";
# Health check endpoint (no auth required)
location = /health {
access_log off;
return 200 'healthy';
add_header Content-Type text/plain;
}
# Authentication endpoint for OAuth2 callback
location = /auth/callback {
limit_req zone=auth burst=10 nodelay;
access_by_lua_block {
local jwt_auth = require "jwt_auth"
local cjson = require "cjson"
-- Handle OAuth2 callback and create JWT
local code = ngx.var.arg_code
if not code then
ngx.status = 400
ngx.say(cjson.encode({error = "missing_code"}))
ngx.exit(400)
end
-- Exchange code for token (implement based on your OAuth2 provider)
-- This is a simplified example
ngx.header["Content-Type"] = "application/json"
ngx.say(cjson.encode({message = "OAuth2 callback processed"}))
}
}
# Public API endpoints (no authentication)
location /api/public {
limit_req zone=api burst=20 nodelay;
# Proxy to your application backend
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# Protected API endpoints (JWT authentication required)
location /api/protected {
limit_req zone=api burst=20 nodelay;
access_by_lua_block {
local jwt_auth = require "jwt_auth"
jwt_auth.authenticate()
}
# Proxy to your application backend with user context
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Auth-User-ID $auth_user_id;
proxy_set_header X-Auth-User-Email $auth_user_email;
proxy_set_header X-Auth-User-Roles $auth_user_roles;
}
# Admin endpoints (requires admin role)
location /api/admin {
limit_req zone=api burst=10 nodelay;
access_by_lua_block {
local jwt_auth = require "jwt_auth"
jwt_auth.authenticate()
jwt_auth.require_role("admin")
}
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Auth-User-ID $auth_user_id;
proxy_set_header X-Auth-User-Email $auth_user_email;
proxy_set_header X-Auth-User-Roles $auth_user_roles;
}
# Static files
location / {
root /var/www/html;
index index.html index.htm;
try_files $uri $uri/ =404;
}
}
HTTP redirect to HTTPS
server {
listen 80;
listen [::]:80;
server_name example.com;
return 301 https://$server_name$request_uri;
}
Create JWT token generation utility
Build a Lua script for generating JWT tokens during development and testing of your authentication system.
local jwt = require "resty.jwt"
local cjson = require "cjson"
local io = require "io"
-- Load private key for signing
local function load_private_key()
local file = io.open("/etc/openresty/ssl/jwt_private.key", "r")
if not file then
error("Could not open JWT private key file")
end
local key = file:read("*all")
file:close()
return key
end
-- Generate JWT token
local function generate_token(payload)
local private_key = load_private_key()
-- Set default expiration (1 hour)
payload.exp = payload.exp or (os.time() + 3600)
payload.iat = payload.iat or os.time()
payload.iss = payload.iss or "openresty-jwt"
local token = jwt:sign(private_key, {
header = {
typ = "JWT",
alg = "RS256"
},
payload = payload
})
return token
end
-- Example usage
if arg and arg[1] then
local payload = {
sub = arg[1] or "user123",
email = arg[2] or "user@example.com",
roles = {arg[3] or "user"}
}
local token = generate_token(payload)
print("JWT Token:")
print(token)
print("\nTest with:")
print("curl -H 'Authorization: Bearer " .. token .. "' https://example.com/api/protected")
else
print("Usage: lua jwt_generator.lua [email] [role]")
print("Example: lua jwt_generator.lua user123 user@example.com admin")
end
return {
generate_token = generate_token
}
Configure systemd service
Create a systemd service file to manage OpenResty with proper security settings and automatic restart capabilities.
[Unit]
Description=OpenResty HTTP Server
After=network.target remote-fs.target nss-lookup.target
[Service]
Type=forking
PIDFile=/var/run/openresty.pid
ExecStartPre=/usr/bin/openresty -t -c /etc/openresty/nginx.conf
ExecStart=/usr/bin/openresty -c /etc/openresty/nginx.conf
ExecReload=/bin/kill -s HUP $MAINPID
KillSignal=SIGQUIT
TimeoutStopSec=5
KillMode=process
PrivateTmp=true
RestartSec=2
Restart=on-failure
User=openresty
Group=openresty
Security settings
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/var/log/openresty /var/run
[Install]
WantedBy=multi-user.target
Create OpenResty user and set permissions
Create a dedicated system user for OpenResty with minimal privileges and proper directory ownership.
sudo useradd --system --no-create-home --shell /bin/false --group openresty openresty
sudo chown -R openresty:openresty /etc/openresty /var/log/openresty
sudo chmod -R 755 /etc/openresty
sudo chmod -R 750 /etc/openresty/ssl /var/log/openresty
sudo chmod 600 /etc/openresty/ssl/jwt_private.key
Enable and start OpenResty service
Enable OpenResty to start automatically on boot and start the service immediately.
sudo systemctl daemon-reload
sudo systemctl enable openresty
sudo systemctl start openresty
Configure firewall rules
Open the necessary ports for HTTP and HTTPS traffic while maintaining security.
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw reload
Verify your setup
Test your OpenResty JWT authentication system to ensure all components are working correctly.
# Check OpenResty service status
sudo systemctl status openresty
Verify configuration syntax
sudo openresty -t -c /etc/openresty/nginx.conf
Generate a test JWT token
cd /etc/openresty/lua
sudo -u openresty lua jwt_generator.lua testuser user@example.com admin
Test public endpoint (should work without token)
curl -i https://example.com/api/public
Test protected endpoint without token (should return 401)
curl -i https://example.com/api/protected
Test protected endpoint with token (replace TOKEN with generated JWT)
curl -i -H "Authorization: Bearer TOKEN" https://example.com/api/protected
Check logs for authentication events
sudo tail -f /var/log/openresty/access.log
Configure OAuth2 provider integration
Update OAuth2 configuration
Modify the JWT authentication module to include your specific OAuth2 provider settings and endpoints.
sudo nano /etc/openresty/lua/jwt_auth.lua
Update the OAuth2 configuration variables at the top of the file:
-- Replace with your OAuth2 provider settings
_M.oauth2_introspect_url = "https://auth.yourprovider.com/oauth/introspect"
_M.oauth2_client_id = "your-actual-client-id"
_M.oauth2_client_secret = "your-actual-client-secret"
_M.oauth2_authorize_url = "https://auth.yourprovider.com/oauth/authorize"
_M.oauth2_token_url = "https://auth.yourprovider.com/oauth/token"
Implement OAuth2 authorization flow
Add a complete OAuth2 authorization flow handler to your application configuration.
local http = require "resty.http"
local cjson = require "cjson"
local jwt = require "resty.jwt"
local _M = {}
-- OAuth2 configuration
_M.client_id = "your-client-id"
_M.client_secret = "your-client-secret"
_M.redirect_uri = "https://example.com/auth/callback"
_M.authorize_url = "https://auth.yourprovider.com/oauth/authorize"
_M.token_url = "https://auth.yourprovider.com/oauth/token"
_M.userinfo_url = "https://auth.yourprovider.com/userinfo"
-- Generate OAuth2 authorization URL
function _M.get_auth_url(state, scope)
local params = {
"response_type=code",
"client_id=" .. _M.client_id,
"redirect_uri=" .. ngx.escape_uri(_M.redirect_uri),
"scope=" .. ngx.escape_uri(scope or "openid profile email"),
"state=" .. ngx.escape_uri(state or ngx.time())
}
return _M.authorize_url .. "?" .. table.concat(params, "&")
end
-- Exchange authorization code for access token
function _M.exchange_code(code, state)
local httpc = http.new()
httpc:set_timeout(10000)
local res, err = httpc:request_uri(_M.token_url, {
method = "POST",
headers = {
["Content-Type"] = "application/x-www-form-urlencoded",
["Accept"] = "application/json"
},
body = table.concat({
"grant_type=authorization_code",
"code=" .. ngx.escape_uri(code),
"client_id=" .. _M.client_id,
"client_secret=" .. _M.client_secret,
"redirect_uri=" .. ngx.escape_uri(_M.redirect_uri)
}, "&")
})
if not res or res.status ~= 200 then
return nil, "Token exchange failed"
end
return cjson.decode(res.body)
end
-- Get user information from OAuth2 provider
function _M.get_userinfo(access_token)
local httpc = http.new()
httpc:set_timeout(5000)
local res, err = httpc:request_uri(_M.userinfo_url, {
method = "GET",
headers = {
["Authorization"] = "Bearer " .. access_token,
["Accept"] = "application/json"
}
})
if not res or res.status ~= 200 then
return nil, "Failed to get user info"
end
return cjson.decode(res.body)
end
return _M
Add OAuth2 login endpoint
Create a login endpoint that redirects users to the OAuth2 provider for authentication.
Add this location block to your /etc/openresty/conf.d/app.conf file:
# OAuth2 login endpoint
location = /auth/login {
access_by_lua_block {
local oauth2 = require "oauth2_handler"
local auth_url = oauth2.get_auth_url()
ngx.redirect(auth_url)
}
}
Update OAuth2 callback handler
Replace the simple callback handler with a complete OAuth2 flow implementation that exchanges codes for tokens.
Replace the existing /auth/callback location block in /etc/openresty/conf.d/app.conf:
# OAuth2 callback endpoint
location = /auth/callback {
limit_req zone=auth burst=10 nodelay;
access_by_lua_block {
local oauth2 = require "oauth2_handler"
local jwt_auth = require "jwt_auth"
local cjson = require "cjson"
local code = ngx.var.arg_code
local state = ngx.var.arg_state
local error = ngx.var.arg_error
if error then
ngx.status = 400
ngx.header["Content-Type"] = "application/json"
ngx.say(cjson.encode({error = "oauth2_error", message = error}))
ngx.exit(400)
end
if not code then
ngx.status = 400
ngx.header["Content-Type"] = "application/json"
ngx.say(cjson.encode({error = "missing_code", message = "Authorization code required"}))
ngx.exit(400)
end
-- Exchange code for tokens
local tokens, err = oauth2.exchange_code(code, state)
if not tokens then
ngx.status = 400
ngx.header["Content-Type"] = "application/json"
ngx.say(cjson.encode({error = "token_exchange_failed", message = err}))
ngx.exit(400)
end
-- Get user information
local userinfo, err = oauth2.get_userinfo(tokens.access_token)
if not userinfo then
ngx.status = 400
ngx.header["Content-Type"] = "application/json"
ngx.say(cjson.encode({error = "userinfo_failed", message = err}))
ngx.exit(400)
end
-- Create JWT payload from OAuth2 user info
local jwt_payload = {
sub = userinfo.sub or userinfo.id,
email = userinfo.email,
name = userinfo.name,
roles = userinfo.roles or {"user"},
exp = ngx.time() + 3600, -- 1 hour
iat = ngx.time(),
iss = "openresty-oauth2"
}
-- Generate internal JWT token
local generator = require "jwt_generator"
local jwt_token = generator.generate_token(jwt_payload)
-- Set secure cookie with JWT
ngx.header["Set-Cookie"] = "auth_token=" .. jwt_token .. "; HttpOnly; Secure; SameSite=Strict; Max-Age=3600; Path=/"
-- Redirect to application
ngx.redirect("/dashboard")
}
}
Reload OpenResty configuration
Apply the new OAuth2 configuration changes by reloading the OpenResty service.
sudo openresty -t -c /etc/openresty/nginx.conf
sudo systemctl reload openresty
Common issues
| Symptom | Cause | Fix |
|---|---|---|
| "JWT verification failed" error | Wrong public key or algorithm mismatch | Check key file permissions and ensure RS256 algorithm |
| "Could not open JWT key file" error | File permissions or missing key file | sudo chmod 644 /etc/openresty/ssl/jwt_public.key |
| OAuth2 introspection timeout | Network connectivity or wrong endpoint | Verify OAuth2 provider URL and network access |
| "No authorization header" error | Missing Bearer token in request | Include Authorization: Bearer token header |
| 403 Forbidden with valid token | Insufficient role permissions | Check user roles in JWT payload and required roles |
| OpenResty won't start | Configuration syntax error | sudo openresty -t to check configuration |
| Module not found error | Lua module path incorrect | Verify lua_package_path in nginx.conf |
| SSL certificate errors | Invalid or self-signed certificates | Install valid SSL certificates or update client trust |
Next steps
- Install and configure OpenResty web server with Lua scripting and performance optimization
- Configure Redis Sentinel with SSL/TLS encryption and authentication for high availability
- Configure Keycloak OAuth2 integration with OpenResty for enterprise SSO
- Implement OpenResty rate limiting and API protection with Lua middleware
- Set up OpenResty monitoring with Prometheus and Grafana dashboards
Automated install script
Run this to automate the entire setup
#!/usr/bin/env bash
set -euo pipefail
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
# Global variables
DOMAIN="${1:-localhost}"
OAUTH2_URL="${2:-https://oauth.example.com/oauth/introspect}"
OAUTH2_CLIENT_ID="${3:-your-client-id}"
OAUTH2_CLIENT_SECRET="${4:-your-client-secret}"
usage() {
echo "Usage: $0 [domain] [oauth2_url] [client_id] [client_secret]"
echo "Example: $0 api.example.com https://auth.example.com/oauth/introspect myapp secret123"
exit 1
}
log() {
echo -e "${GREEN}[INFO]${NC} $1"
}
warn() {
echo -e "${YELLOW}[WARN]${NC} $1"
}
error() {
echo -e "${RED}[ERROR]${NC} $1"
exit 1
}
cleanup() {
if [ $? -ne 0 ]; then
warn "Installation failed. Cleaning up..."
systemctl stop openresty 2>/dev/null || true
rm -rf /etc/openresty/conf.d/jwt-auth.conf 2>/dev/null || true
fi
}
trap cleanup ERR
check_prerequisites() {
if [ "$EUID" -ne 0 ]; then
error "This script must be run as root or with sudo"
fi
if ! command -v curl &> /dev/null; then
error "curl is required but not installed"
fi
}
detect_distro() {
if [ ! -f /etc/os-release ]; then
error "Cannot detect distribution"
fi
. /etc/os-release
case "$ID" in
ubuntu|debian)
PKG_MGR="apt"
PKG_INSTALL="apt install -y"
PKG_UPDATE="apt update && apt upgrade -y"
OPENRESTY_USER="www-data"
OPENRESTY_GROUP="www-data"
;;
almalinux|rocky|centos|rhel|ol|fedora)
PKG_MGR="dnf"
PKG_INSTALL="dnf install -y"
PKG_UPDATE="dnf update -y"
OPENRESTY_USER="nobody"
OPENRESTY_GROUP="nobody"
;;
amzn)
PKG_MGR="yum"
PKG_INSTALL="yum install -y"
PKG_UPDATE="yum update -y"
OPENRESTY_USER="nobody"
OPENRESTY_GROUP="nobody"
;;
*)
error "Unsupported distribution: $ID"
;;
esac
}
install_openresty() {
case "$PKG_MGR" in
apt)
$PKG_INSTALL wget gnupg ca-certificates
wget -qO - https://openresty.org/package/pubkey.gpg | apt-key add -
echo "deb http://openresty.org/package/ubuntu $(lsb_release -sc) main" > /etc/apt/sources.list.d/openresty.list
apt update
$PKG_INSTALL openresty luarocks git build-essential libssl-dev
;;
dnf|yum)
$PKG_INSTALL wget
wget -O /etc/yum.repos.d/openresty.repo https://openresty.org/package/rhel/openresty.repo
$PKG_INSTALL openresty luarocks git gcc gcc-c++ openssl-devel
;;
esac
}
install_lua_modules() {
luarocks install lua-resty-jwt
luarocks install lua-resty-http
luarocks install lua-cjson
}
create_directories() {
mkdir -p /etc/openresty/{conf.d,lua,ssl}
mkdir -p /var/log/openresty
chown -R $OPENRESTY_USER:$OPENRESTY_GROUP /etc/openresty /var/log/openresty
chmod 755 /etc/openresty /var/log/openresty
chmod 750 /etc/openresty/ssl
}
generate_jwt_keys() {
openssl genrsa -out /etc/openresty/ssl/jwt_private.key 2048
openssl rsa -in /etc/openresty/ssl/jwt_private.key -pubout -out /etc/openresty/ssl/jwt_public.key
chown $OPENRESTY_USER:$OPENRESTY_GROUP /etc/openresty/ssl/jwt_*.key
chmod 600 /etc/openresty/ssl/jwt_private.key
chmod 644 /etc/openresty/ssl/jwt_public.key
}
create_jwt_module() {
cat > /etc/openresty/lua/jwt_auth.lua << 'EOF'
local jwt = require "resty.jwt"
local http = require "resty.http"
local cjson = require "cjson"
local io = require "io"
local _M = {}
_M.jwt_secret_key = nil
_M.oauth2_introspect_url = os.getenv("OAUTH2_INTROSPECT_URL") or "https://oauth.example.com/oauth/introspect"
_M.oauth2_client_id = os.getenv("OAUTH2_CLIENT_ID") or "your-client-id"
_M.oauth2_client_secret = os.getenv("OAUTH2_CLIENT_SECRET") or "your-client-secret"
function _M.load_jwt_key()
local file = io.open("/etc/openresty/ssl/jwt_public.key", "r")
if not file then
ngx.log(ngx.ERR, "Could not open JWT public key file")
return nil
end
local key = file:read("*all")
file:close()
return key
end
function _M.validate_jwt(token)
if not _M.jwt_secret_key then
_M.jwt_secret_key = _M.load_jwt_key()
if not _M.jwt_secret_key then
return false, "JWT key not available"
end
end
local jwt_token = jwt:verify(_M.jwt_secret_key, token, {
alg = "RS256"
})
if not jwt_token.verified then
return false, "Invalid JWT token"
end
local current_time = ngx.time()
if jwt_token.payload.exp and jwt_token.payload.exp < current_time then
return false, "Token expired"
end
return true, jwt_token.payload
end
function _M.introspect_token(access_token)
local httpc = http.new()
local res, err = httpc:request_uri(_M.oauth2_introspect_url, {
method = "POST",
headers = {
["Content-Type"] = "application/x-www-form-urlencoded",
["Authorization"] = "Basic " .. ngx.encode_base64(_M.oauth2_client_id .. ":" .. _M.oauth2_client_secret)
},
body = "token=" .. access_token
})
if not res then
return false, "Failed to introspect token: " .. err
end
if res.status ~= 200 then
return false, "Token introspection failed"
end
local result = cjson.decode(res.body)
return result.active == true, result
end
function _M.authenticate()
local auth_header = ngx.var.http_authorization
if not auth_header then
ngx.status = 401
ngx.header["WWW-Authenticate"] = "Bearer"
ngx.say('{"error":"Missing Authorization header"}')
ngx.exit(401)
end
local token = auth_header:match("Bearer%s+(.+)")
if not token then
ngx.status = 401
ngx.say('{"error":"Invalid Authorization header format"}')
ngx.exit(401)
end
local valid, payload = _M.validate_jwt(token)
if valid then
ngx.ctx.user = payload
return
end
valid, payload = _M.introspect_token(token)
if not valid then
ngx.status = 401
ngx.say('{"error":"Invalid or expired token"}')
ngx.exit(401)
end
ngx.ctx.user = payload
end
return _M
EOF
chown $OPENRESTY_USER:$OPENRESTY_GROUP /etc/openresty/lua/jwt_auth.lua
chmod 644 /etc/openresty/lua/jwt_auth.lua
}
create_nginx_config() {
cat > /etc/openresty/conf.d/jwt-auth.conf << EOF
lua_package_path "/etc/openresty/lua/?.lua;;";
server {
listen 80;
server_name $DOMAIN;
location /health {
access_log off;
return 200 "OK";
add_header Content-Type text/plain;
}
location /auth {
access_by_lua_block {
local jwt_auth = require "jwt_auth"
jwt_auth.authenticate()
}
proxy_set_header X-User-ID \$ctx_user_sub;
proxy_set_header X-User-Email \$ctx_user_email;
proxy_pass http://backend;
}
location / {
return 404;
}
}
upstream backend {
server 127.0.0.1:8080;
}
EOF
chown $OPENRESTY_USER:$OPENRESTY_GROUP /etc/openresty/conf.d/jwt-auth.conf
chmod 644 /etc/openresty/conf.d/jwt-auth.conf
}
configure_main_nginx() {
if [ ! -f /etc/openresty/nginx.conf.backup ]; then
cp /usr/local/openresty/nginx/conf/nginx.conf /etc/openresty/nginx.conf.backup
fi
cat > /usr/local/openresty/nginx/conf/nginx.conf << EOF
user $OPENRESTY_USER;
worker_processes auto;
error_log /var/log/openresty/error.log;
pid /var/run/openresty.pid;
events {
worker_connections 1024;
}
http {
log_format main '\$remote_addr - \$remote_user [\$time_local] "\$request" '
'\$status \$body_bytes_sent "\$http_referer" '
'"\$http_user_agent" "\$http_x_forwarded_for"';
access_log /var/log/openresty/access.log main;
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
include /usr/local/openresty/nginx/conf/mime.types;
default_type application/octet-stream;
include /etc/openresty/conf.d/*.conf;
}
EOF
}
setup_systemd_service() {
systemctl enable openresty
systemctl start openresty
}
configure_firewall() {
if command -v ufw &> /dev/null; then
ufw --force enable
ufw allow 80/tcp
ufw allow 443/tcp
elif command -v firewall-cmd &> /dev/null; then
systemctl enable firewalld
systemctl start firewalld
firewall-cmd --permanent --add-service=http
firewall-cmd --permanent --add-service=https
firewall-cmd --reload
fi
}
verify_installation() {
if ! systemctl is-active --quiet openresty; then
error "OpenResty is not running"
fi
if ! curl -s http://localhost/health | grep -q "OK"; then
error "Health check failed"
fi
log "OpenResty JWT authentication is configured and running"
log "Health check: http://$DOMAIN/health"
log "Auth endpoint: http://$DOMAIN/auth"
log "Remember to update OAuth2 credentials in environment variables"
}
main() {
log "[1/11] Checking prerequisites..."
check_prerequisites
log "[2/11] Detecting distribution..."
detect_distro
log "[3/11] Updating system packages..."
$PKG_UPDATE
log "[4/11] Installing OpenResty and dependencies..."
install_openresty
log "[5/11] Installing Lua modules..."
install_lua_modules
log "[6/11] Creating directory structure..."
create_directories
log "[7/11] Generating JWT signing keys..."
generate_jwt_keys
log "[8/11] Creating JWT authentication module..."
create_jwt_module
log "[9/11] Configuring OpenResty..."
configure_main_nginx
create_nginx_config
log "[10/11] Starting services..."
setup_systemd_service
configure_firewall
log "[11/11] Verifying installation..."
verify_installation
log "Installation completed successfully!"
}
if [ $# -gt 4 ]; then
usage
fi
main "$@"
Review the script before running. Execute with: bash install.sh