Configure Keycloak OAuth2 integration with OpenResty for enterprise SSO

Advanced 45 min Apr 17, 2026
Ubuntu 24.04 Debian 12 AlmaLinux 9 Rocky Linux 9

Set up enterprise single sign-on by integrating Keycloak OAuth2 authentication with OpenResty using lua-resty-openidc. Configure secure authentication flows, JWT token validation, and session management for production web applications.

Prerequisites

  • Root access to the server
  • Domain name with SSL certificate
  • Running Keycloak instance
  • Basic knowledge of Lua and NGINX

What this solves

This tutorial configures Keycloak OAuth2 integration with OpenResty to provide enterprise single sign-on (SSO) authentication. You'll implement secure authentication flows using lua-resty-openidc, validate JWT tokens, and manage user sessions across multiple applications.

Step-by-step configuration

Update system packages and install dependencies

Start by updating your system and installing required packages for OpenResty and Lua modules.

sudo apt update && sudo apt upgrade -y
sudo apt install -y wget gnupg2 software-properties-common curl
sudo dnf update -y
sudo dnf install -y wget gnupg2 curl epel-release

Install OpenResty with Lua modules

Install OpenResty repository and the main package with required Lua modules for OAuth2 integration.

wget -qO - https://openresty.org/package/pubkey.gpg | sudo gpg --dearmor -o /usr/share/keyrings/openresty.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/openresty.gpg] 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 openresty-opm luarocks
sudo yum-config-manager --add-repo https://openresty.org/package/rhel/openresty.repo
sudo dnf install -y openresty openresty-opm luarocks

Install lua-resty-openidc module

Install the OpenID Connect client library for Lua that handles OAuth2 authentication flows with Keycloak.

sudo /usr/local/openresty/bin/opm get zmartzone/lua-resty-openidc
sudo luarocks install lua-resty-jwt
sudo luarocks install lua-resty-session
sudo luarocks install lua-resty-http

Create OpenResty directory structure

Set up the directory structure for OpenResty configuration files and Lua scripts.

sudo mkdir -p /etc/openresty/conf.d
sudo mkdir -p /etc/openresty/lua
sudo mkdir -p /var/log/openresty
sudo chown -R nobody:nobody /var/log/openresty

Configure Keycloak OAuth2 client

Create the Keycloak client configuration that OpenResty will use for OAuth2 authentication.

Note: You need access to your Keycloak admin console to create the OAuth2 client. This tutorial assumes you have Keycloak already running.

In your Keycloak admin console, create a new client with these settings:

  • Client ID: openresty-client
  • Client Protocol: openid-connect
  • Access Type: confidential
  • Valid Redirect URIs: https://example.com/auth
  • Web Origins: https://example.com

Save the client secret from the Credentials tab for the next step.

Create OpenResty main configuration

Configure the main OpenResty nginx.conf file with Lua module paths and basic settings.

user nobody;
worker_processes auto;
error_log /var/log/openresty/error.log;
pid /run/openresty.pid;

events {
    worker_connections 1024;
    use epoll;
}

http {
    lua_package_path '/usr/local/openresty/site/lualib/?.lua;/etc/openresty/lua/?.lua;;';
    lua_package_cpath '/usr/local/openresty/site/lualib/?.so;;';
    
    lua_shared_dict discovery 1m;
    lua_shared_dict jwks 1m;
    lua_shared_dict introspection 10m;
    
    resolver 8.8.8.8 8.8.4.4 valid=300s;
    resolver_timeout 5s;
    
    include /etc/openresty/mime.types;
    default_type application/octet-stream;
    
    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 4096;
    
    include /etc/openresty/conf.d/*.conf;
}

Create Lua authentication script

Create the main Lua script that handles OAuth2 authentication flows with Keycloak.

local openidc = require("resty.openidc")
local cjson = require("cjson")

-- Keycloak configuration
local opts = {
  redirect_uri = "https://example.com/auth",
  discovery = "https://keycloak.example.com/realms/master/.well-known/openid_configuration",
  client_id = "openresty-client",
  client_secret = "your-client-secret-here",
  scope = "openid profile email",
  
  -- Session configuration
  session_opts = {
    secret = "change-this-to-a-random-32-character-string",
    cookie_name = "OIDC_SESSION",
    cookie_lifetime = 3600,
    cookie_secure = true,
    cookie_httponly = true,
    cookie_samesite = "Lax"
  },
  
  -- Token validation
  token_signing_alg_values_expected = { "RS256" },
  accept_none_alg = false,
  
  -- Security settings
  ssl_verify = "yes",
  timeout = 10,
  
  -- Logout configuration
  logout_path = "/logout",
  post_logout_redirect_uri = "https://example.com/"
}

-- Handle authentication
local res, err = openidc.authenticate(opts)

if err then
  ngx.log(ngx.ERR, "Authentication error: ", err)
  ngx.status = 500
  ngx.say("Authentication failed")
  ngx.exit(500)
end

-- Set user information in headers
if res.user then
  ngx.req.set_header("X-User-Id", res.user.sub or "")
  ngx.req.set_header("X-User-Email", res.user.email or "")
  ngx.req.set_header("X-User-Name", res.user.name or "")
  ngx.req.set_header("X-User-Groups", cjson.encode(res.user.groups or {}))
end

-- Log successful authentication
ngx.log(ngx.INFO, "User authenticated: ", res.user.email or "unknown")

Create virtual host configuration

Configure a virtual host that uses the Lua authentication script to protect web applications.

server {
    listen 443 ssl http2;
    server_name example.com;
    
    # SSL configuration
    ssl_certificate /etc/ssl/certs/example.com.crt;
    ssl_certificate_key /etc/ssl/private/example.com.key;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384;
    ssl_prefer_server_ciphers off;
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 10m;
    
    # Security headers
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
    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;
    
    # OAuth2 callback endpoint
    location /auth {
        access_by_lua_file /etc/openresty/lua/auth.lua;
        return 302 /;
    }
    
    # Logout endpoint
    location /logout {
        access_by_lua_block {
            local openidc = require("resty.openidc")
            local opts = {
                discovery = "https://keycloak.example.com/realms/master/.well-known/openid_configuration",
                post_logout_redirect_uri = "https://example.com/"
            }
            openidc.logout(opts)
        }
    }
    
    # Protected application routes
    location / {
        access_by_lua_file /etc/openresty/lua/auth.lua;
        
        # Proxy to backend application
        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;
        
        # Pass user information to backend
        proxy_set_header X-User-Id $http_x_user_id;
        proxy_set_header X-User-Email $http_x_user_email;
        proxy_set_header X-User-Name $http_x_user_name;
        proxy_set_header X-User-Groups $http_x_user_groups;
        
        proxy_connect_timeout 5s;
        proxy_send_timeout 60s;
        proxy_read_timeout 60s;
        proxy_buffering on;
        proxy_buffer_size 8k;
        proxy_buffers 8 8k;
    }
    
    # Health check endpoint (no authentication required)
    location /health {
        access_log off;
        return 200 "OK";
        add_header Content-Type text/plain;
    }
    
    # Static assets (no authentication required)
    location /static {
        root /var/www/html;
        expires 1y;
        add_header Cache-Control "public, immutable";
    }
}

Create systemd service file

Create a systemd service file for better OpenResty management and automatic startup.

[Unit]
Description=OpenResty Web Server
After=network.target

[Service]
Type=forking
PIDFile=/run/openresty.pid
ExecStartPre=/usr/local/openresty/bin/openresty -t -c /etc/openresty/nginx.conf
ExecStart=/usr/local/openresty/bin/openresty -c /etc/openresty/nginx.conf
ExecReload=/bin/kill -s HUP $MAINPID
ExecStop=/bin/kill -s QUIT $MAINPID
PrivateTmp=true

[Install]
WantedBy=multi-user.target

Set correct file permissions

Configure proper ownership and permissions for OpenResty configuration files and directories.

sudo chown -R root:root /etc/openresty
sudo chmod 755 /etc/openresty
sudo chmod 755 /etc/openresty/conf.d
sudo chmod 755 /etc/openresty/lua
sudo chmod 644 /etc/openresty/nginx.conf
sudo chmod 644 /etc/openresty/conf.d/*.conf
sudo chmod 644 /etc/openresty/lua/*.lua
Never use chmod 777. It gives every user on the system full access to your files. The permissions above provide secure access while allowing OpenResty to read configuration files.

Create log rotation configuration

Set up log rotation to prevent OpenResty logs from filling up disk space.

/var/log/openresty/*.log {
    daily
    missingok
    rotate 14
    compress
    delaycompress
    notifempty
    create 644 nobody nobody
    postrotate
        if [ -f /run/openresty.pid ]; then
            kill -USR1 cat /run/openresty.pid
        fi
    endscript
}

Enable and start OpenResty service

Enable the OpenResty service to start automatically on boot and start it now.

sudo systemctl daemon-reload
sudo systemctl enable openresty
sudo systemctl start openresty
sudo systemctl status openresty

Configure firewall rules

Open the necessary ports for HTTPS traffic and close unnecessary ports.

sudo ufw allow 443/tcp comment 'OpenResty HTTPS'
sudo ufw allow 80/tcp comment 'OpenResty HTTP redirect'
sudo ufw reload
sudo firewall-cmd --permanent --add-service=https
sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --reload

Verify your setup

Test the OpenResty configuration and OAuth2 integration with these verification commands.

sudo /usr/local/openresty/bin/openresty -t -c /etc/openresty/nginx.conf
sudo systemctl status openresty
curl -I https://example.com/health
grep -i error /var/log/openresty/error.log | tail -5

Test the OAuth2 flow by accessing your protected application:

curl -L -I https://example.com/

Should redirect to Keycloak login page

grep "User authenticated" /var/log/openresty/access.log | tail -5

Advanced security configuration

Configure session security

Enhance session security with additional protection mechanisms.

local openidc = require("resty.openidc")
local cjson = require("cjson")

-- Enhanced security configuration
local opts = {
  session_opts = {
    secret = "your-32-character-session-secret-key",
    cookie_name = "OIDC_SESSION",
    cookie_lifetime = 1800,  -- 30 minutes
    cookie_secure = true,
    cookie_httponly = true,
    cookie_samesite = "Strict",
    storage = "cookie",
    check_ssi = false
  },
  
  -- Token refresh settings
  refresh_session_interval = 900,  -- 15 minutes
  access_token_expires_in = 300,   -- 5 minutes
  
  -- Rate limiting
  rate_limit_count = 10,
  rate_limit_period = 60,
  
  -- IP whitelist (optional)
  -- whitelist = { "203.0.113.0/24", "198.51.100.0/24" }
}

-- Implement rate limiting
local limit_req = require("resty.limit.req")
local lim, err = limit_req.new("rl_store", 10, 5)
if not lim then
  ngx.log(ngx.ERR, "Failed to instantiate rate limiter: ", err)
  return ngx.exit(500)
end

local key = ngx.var.binary_remote_addr
local delay, err = lim:incoming(key, true)
if not delay then
  if err == "rejected" then
    ngx.log(ngx.WARN, "Rate limit exceeded for ", ngx.var.remote_addr)
    return ngx.exit(429)
  end
  ngx.log(ngx.ERR, "Rate limiter error: ", err)
  return ngx.exit(500)
end

-- Continue with authentication
local res, err = openidc.authenticate(opts)
if err then
  ngx.log(ngx.ERR, "Authentication failed: ", err)
  return ngx.exit(500)
end

Common issues

SymptomCauseFix
Authentication loop redirectsIncorrect redirect URI in Keycloak clientVerify redirect URI matches exactly: https://example.com/auth
SSL certificate errorsMissing or invalid SSL certificatesInstall valid SSL certificates or use Let's Encrypt: certbot --nginx
Lua module not found errorsIncorrect lua_package_path configurationCheck paths in nginx.conf and verify module installation with opm list
Session cookie not setInsecure cookie settings over HTTPEnsure cookie_secure is false for HTTP or use HTTPS with proper SSL setup
Keycloak discovery failsDNS resolution or connectivity issuesTest connectivity: curl https://keycloak.example.com/realms/master/.well-known/openid_configuration
403 Forbidden on static filesIncorrect file permissions or ownershipSet proper permissions: sudo chown -R www-data:www-data /var/www/html && sudo chmod -R 644 /var/www/html

Performance optimization

For production environments, consider implementing these performance enhancements that work well with the OpenResty PostgreSQL connection pooling tutorial.

Configure Lua shared dictionaries

Optimize memory usage and caching for OAuth2 tokens and session data.

# Add to http block
lua_shared_dict discovery 10m;
lua_shared_dict jwks 10m;
lua_shared_dict introspection 50m;
lua_shared_dict sessions 100m;
lua_shared_dict rl_store 10m;  # Rate limiting

Next steps

Automated install script

Run this to automate the entire setup

Need help?

Don't want to manage this yourself?

We handle infrastructure security hardening for businesses that depend on uptime. From initial setup to ongoing operations.