OAuth2 Proxy Installation for Application Authentication

OAuth2 Proxy is a reverse proxy that adds OAuth2/OIDC authentication in front of any web application without modifying the application's code, supporting providers like Google, GitHub, Keycloak, and any OIDC-compliant identity provider. This guide covers deploying OAuth2 Proxy, configuring Google and GitHub providers, integrating with Nginx, and setting access policies.

Prerequisites

  • A Linux server with an application to protect (Grafana, Jupyter, etc.)
  • A domain name with SSL configured
  • Nginx installed as a reverse proxy
  • An OAuth2 provider account (Google Cloud Console, GitHub, or Keycloak)
  • Access to configure DNS and SSL certificates

Installing OAuth2 Proxy

# Download the latest OAuth2 Proxy binary
OAUTH2_PROXY_VERSION=7.7.1
curl -L "https://github.com/oauth2-proxy/oauth2-proxy/releases/download/v${OAUTH2_PROXY_VERSION}/oauth2-proxy-v${OAUTH2_PROXY_VERSION}.linux-amd64.tar.gz" \
  -o /tmp/oauth2-proxy.tar.gz

tar -xzf /tmp/oauth2-proxy.tar.gz -C /tmp/
sudo mv /tmp/oauth2-proxy-v${OAUTH2_PROXY_VERSION}.linux-amd64/oauth2-proxy /usr/local/bin/
sudo chmod +x /usr/local/bin/oauth2-proxy

# Verify installation
oauth2-proxy --version

# Create a system user
sudo useradd -r -s /sbin/nologin oauth2proxy
sudo mkdir -p /etc/oauth2proxy

Configuring the Google Provider

Create OAuth2 Credentials in Google Cloud Console

  1. Go to Google Cloud Console > APIs & Services > Credentials
  2. Click Create Credentials > OAuth 2.0 Client IDs
  3. Application type: Web application
  4. Add authorized redirect URI: https://your-app.example.com/oauth2/callback
  5. Copy the Client ID and Client Secret

OAuth2 Proxy Configuration

# Generate a random cookie secret (32 bytes, base64 encoded)
COOKIE_SECRET=$(python3 -c "import secrets; print(secrets.token_urlsafe(24))")

sudo tee /etc/oauth2proxy/oauth2proxy.cfg << EOF
# Provider
provider = "google"
client_id = "YOUR_GOOGLE_CLIENT_ID.apps.googleusercontent.com"
client_secret = "YOUR_GOOGLE_CLIENT_SECRET"

# Callback URL (must match what you set in Google Console)
redirect_url = "https://app.example.com/oauth2/callback"

# Cookie security
cookie_secret = "${COOKIE_SECRET}"
cookie_secure = true
cookie_httponly = true
cookie_samesite = "lax"
cookie_expire = "168h"   # 7 days

# Session
session_store_type = "cookie"

# Upstream (the app to protect)
upstreams = ["http://localhost:3000"]

# Bind address
http_address = "127.0.0.1:4180"

# Allowed domains (restrict to your org)
email_domains = ["example.com"]

# Or allow specific emails:
# authenticated_emails_file = "/etc/oauth2proxy/allowed-emails.txt"
EOF

sudo chown -R oauth2proxy:oauth2proxy /etc/oauth2proxy
sudo chmod 600 /etc/oauth2proxy/oauth2proxy.cfg

Configuring the GitHub Provider

Create a GitHub OAuth App

  1. Go to GitHub Settings > Developer settings > OAuth Apps > New OAuth App
  2. Set Authorization callback URL: https://app.example.com/oauth2/callback
  3. Copy the Client ID and generate a Client Secret

Configuration File

sudo tee /etc/oauth2proxy/oauth2proxy.cfg << EOF
# Provider
provider = "github"
client_id = "YOUR_GITHUB_CLIENT_ID"
client_secret = "YOUR_GITHUB_CLIENT_SECRET"

redirect_url = "https://app.example.com/oauth2/callback"

cookie_secret = "$(python3 -c "import secrets; print(secrets.token_urlsafe(24))")"
cookie_secure = true
cookie_httponly = true
cookie_expire = "168h"

upstreams = ["http://localhost:3000"]
http_address = "127.0.0.1:4180"

# Restrict by GitHub organization
github_org = "your-github-org"

# Or restrict by GitHub team
# github_org = "your-github-org"
# github_team = "your-team"

# Or allow specific users
# github_users = "user1,user2"
EOF

Configuring a Keycloak/OIDC Provider

# Get the OIDC discovery URL from Keycloak:
# https://auth.example.com/realms/myapp/.well-known/openid-configuration

sudo tee /etc/oauth2proxy/oauth2proxy.cfg << EOF
# Generic OIDC provider (works with Keycloak, Authentik, etc.)
provider = "oidc"
provider_display_name = "Company SSO"

oidc_issuer_url = "https://auth.example.com/realms/myapp"
client_id = "oauth2-proxy-client"
client_secret = "YOUR_KEYCLOAK_CLIENT_SECRET"

redirect_url = "https://app.example.com/oauth2/callback"

cookie_secret = "$(python3 -c "import secrets; print(secrets.token_urlsafe(24))")"
cookie_secure = true
cookie_expire = "24h"

upstreams = ["http://localhost:3000"]
http_address = "127.0.0.1:4180"

# Allow all authenticated users from the realm
email_domains = ["*"]

# Or restrict by email domain
# email_domains = ["example.com"]

# Request additional scopes
scope = "openid email profile groups"

# Pass user info to upstream as headers
set_xauthrequest = true
pass_access_token = true
EOF

Nginx Integration

The most common pattern is to use Nginx as the front-end SSL terminator with OAuth2 Proxy handling authentication:

sudo tee /etc/nginx/sites-available/protected-app << 'EOF'
server {
    listen 80;
    server_name app.example.com;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl;
    server_name app.example.com;

    ssl_certificate /etc/letsencrypt/live/app.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/app.example.com/privkey.pem;

    # OAuth2 Proxy internal auth endpoint
    location /oauth2/ {
        proxy_pass http://127.0.0.1:4180;
        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-Request-Redirect $request_uri;
    }

    # Auth verification endpoint
    location = /oauth2/auth {
        proxy_pass http://127.0.0.1:4180;
        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 Content-Length "";
        proxy_pass_request_body off;
    }

    # Protected application
    location / {
        auth_request /oauth2/auth;
        error_page 401 = /oauth2/sign_in;

        # Pass user information to the upstream app
        auth_request_set $user $upstream_http_x_auth_request_user;
        auth_request_set $email $upstream_http_x_auth_request_email;
        proxy_set_header X-User $user;
        proxy_set_header X-Email $email;

        # Proxy to the actual application
        proxy_pass http://localhost:3000;
        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;

        # WebSocket support
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }
}
EOF

sudo ln -s /etc/nginx/sites-available/protected-app /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
# Cookie configuration for security
cookie_secure = true         # HTTPS only
cookie_httponly = true       # No JavaScript access
cookie_samesite = "lax"      # CSRF protection
cookie_expire = "168h"       # 7-day sessions
cookie_refresh = "1h"        # Refresh token every hour

# Cookie domain for subdomain sharing
cookie_domains = [".example.com"]  # Allows *.example.com to share session
cookie_name = "_oauth2_proxy"      # Custom cookie name

# Redis session store (for multi-server deployments)
session_store_type = "redis"
redis_connection_url = "redis://localhost:6379"

Access Control Policies

# Allow all email domains (any authenticated user)
email_domains = ["*"]

# Restrict to specific domains
email_domains = ["example.com", "partner.com"]

# Allow specific email addresses only
authenticated_emails_file = "/etc/oauth2proxy/allowed-emails.txt"
# File contains one email per line:
# [email protected]
# [email protected]

# Skip authentication for certain paths (health checks, public assets)
skip_auth_routes = [
  "GET=^/health$",
  "GET=^/public/.*"
]

# Allowed IP ranges (bypass auth for internal networks)
trusted_ips = ["10.0.0.0/8", "192.168.0.0/16"]

Running as a Systemd Service

sudo tee /etc/systemd/system/oauth2proxy.service << 'EOF'
[Unit]
Description=OAuth2 Proxy
After=network.target

[Service]
Type=simple
User=oauth2proxy
ExecStart=/usr/local/bin/oauth2-proxy --config=/etc/oauth2proxy/oauth2proxy.cfg
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target
EOF

sudo systemctl daemon-reload
sudo systemctl enable oauth2proxy
sudo systemctl start oauth2proxy

# Check status
sudo systemctl status oauth2proxy
sudo journalctl -u oauth2proxy -f

Troubleshooting

"Invalid state parameter" on callback

# This usually means the cookie was lost — check:
# 1. cookie_secret is consistent across restarts
# 2. redirect_url exactly matches what's registered in the provider
# 3. SSL is working (cookies require HTTPS in production)

"Access denied" after authentication

# Check the email_domains setting
# View oauth2-proxy logs to see which email authenticated
sudo journalctl -u oauth2proxy | grep "authenticated"

# Verify email is in the allowed list
grep [email protected] /etc/oauth2proxy/allowed-emails.txt

404 on /oauth2/callback

# Nginx must proxy /oauth2/ to oauth2-proxy
# Verify the /oauth2/ location block exists in Nginx config

# Test oauth2-proxy is running
curl http://127.0.0.1:4180/ping

"Token verification failed"

# For OIDC providers — verify the JWKS endpoint is accessible
curl "https://auth.example.com/realms/myapp/protocol/openid-connect/certs"

# Check time sync (JWT validation requires accurate time)
timedatectl

Conclusion

OAuth2 Proxy provides a universal authentication layer for any web application without requiring code changes, making it ideal for adding SSO to internal tools like Grafana, Jupyter, and custom dashboards. By integrating with Nginx's auth_request directive and connecting to any OIDC provider, you get a flexible, production-ready authentication gateway that handles all OAuth2 flows transparently.