Stop Leaving Your Internal Tools Exposed: A Guide to OAuth2 Proxy

Security tutorial - IT technology blog
Security tutorial - IT technology blog

Securing the Unsecured

We’ve all been there: you spin up a Prometheus dashboard, a private Wiki, or a custom admin script, only to realize it has zero built-in security. Leaving these open on a local network is a gamble. Putting them on the public web without a password is just asking for a data breach.

Instead of hacking together a fragile basic auth system, you should use OAuth2 Proxy. It acts as a professional-grade gatekeeper. It sits in front of your service and forces users to log in via Google, GitHub, or OpenID Connect before they can see a single byte of your data.

Quick Start: Secure an App in 5 Minutes

The fastest way to see this in action is with Docker Compose. In this example, we will protect a simple ‘Whoami’ service using GitHub as our identity provider.

1. Create a GitHub OAuth App

Head over to your GitHub Settings > Developer settings > OAuth Apps > New OAuth App. Set the Homepage URL to http://localhost:4180 and the Authorization callback URL to http://localhost:4180/oauth2/callback. Once created, grab your Client ID and Client Secret.

2. The Docker Compose File

Next, create a docker-compose.yml file. For the OAUTH2_PROXY_COOKIE_SECRET, you need a precise 32-byte string. I usually use the generator at toolcraft.app because it runs entirely in your browser. If this secret isn’t exactly 32 characters, the proxy will fail to start.

version: '3'
services:
  oauth2-proxy:
    image: quay.io/oauth2-proxy/oauth2-proxy:latest
    ports:
      - "4180:4180"
    environment:
      OAUTH2_PROXY_PROVIDER: github
      OAUTH2_PROXY_CLIENT_ID: "YOUR_CLIENT_ID"
      OAUTH2_PROXY_CLIENT_SECRET: "YOUR_CLIENT_SECRET"
      OAUTH2_PROXY_COOKIE_SECRET: "YOUR_32_BYTE_SECRET"
      OAUTH2_PROXY_UPSTREAM: "http://webapp:80"
      OAUTH2_PROXY_HTTP_ADDRESS: "0.0.0.0:4180"
      OAUTH2_PROXY_EMAIL_DOMAINS: "*"

  webapp:
    image: traefik/whoami
    container_name: webapp

Fire it up with docker-compose up -d. Now, visit http://localhost:4180. You’ll be blocked by a GitHub login screen. Only after a successful login will the “Whoami” page appear.

Under the Hood: How it Works

OAuth2 Proxy functions as a reverse proxy. When a request arrives, it looks for a specific session cookie. If that cookie is missing or has expired, the proxy bounces the user to the provider (like Google). After the user logs in, the provider sends them back with an authorization code. The proxy then trades this code for a token, drops a secure cookie in the browser, and finally lets the traffic through to your app.

Key Configuration Details

  • Upstreams: This is the internal URL of your app. Traffic only reaches this destination after the proxy verifies the user’s identity.
  • Email Domains: This is your primary defense. Instead of using *, set this to yourcompany.com. This ensures that even if someone has a GitHub account, they can’t get in unless they have a company email.
  • Cookie Secret: This signs your session cookies to prevent tampering. If this key is compromised, an attacker could forge a login session.

Switching to Google Auth

Google is the standard for most corporate setups. To use it, create credentials in the Google Cloud Console under “OAuth client ID.” Select “Web application” and set your redirect URI to https://your-domain.com/oauth2/callback. In your config, simply swap the provider to google and update the IDs.

Advanced Setup: Nginx Integration

In production, I rarely expose OAuth2 Proxy directly. It’s better to use Nginx as the main entry point and let it talk to the proxy via the auth_request module. This allows a single OAuth2 Proxy instance to protect dozens of different subdomains.

Here is how you tell Nginx to check the proxy before granting access:

server {
    listen 80;
    server_name private-app.example.com;

    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-Auth-Request-Redirect $request_uri;
    }

    location / {
        auth_request /oauth2/auth;
        error_page 401 = /oauth2/sign_in;

        # Pass verified user info to your 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_pass http://internal-service:8080;
    }
}

This approach is incredibly clean. Your internal service doesn’t need to know anything about OAuth or tokens. It simply reads the X-User header, which Nginx only provides after the proxy has done the heavy lifting.

Battle-Tested Production Tips

Moving from a local test to a live environment usually reveals a few hurdles. Here is what I’ve learned from managing these deployments.

1. Solving the “Large Cookie” Problem

OAuth2 Proxy stores session data in the cookie by default. If your users belong to many groups, the token size can easily exceed the 4KB browser limit, causing random 400 Bad Request errors. To fix this, switch to Redis for session storage. The cookie then becomes a tiny session ID, while the heavy data stays on your server.

2. Granular Access Control

Restricting by domain is often too broad. If you use GitHub, use the --github-org="your-org" flag to limit access to specific organization members. For Google Workspace users, the --google-admin-group flag allows you to restrict apps to specific departments, like [email protected].

3. The “Login Loop” Trap

Always set --cookie-secure=true in production. This forces the cookie to only travel over HTTPS. However, if you are testing on HTTP/localhost, this setting will cause a login loop because the browser will silently reject the insecure cookie. Turn it off for local dev, but never for production.

4. Debugging Mismatches

If you get a “Redirect URI Mismatch” error, double-check your trailing slashes. OAuth providers are incredibly picky. http://app.com/oauth2 and http://app.com/oauth2/ are different URLs to them. When in doubt, check the container logs; they usually point exactly to the URL the provider was expecting.

By implementing OAuth2 Proxy, you’ve added a robust security layer to tools that were never built for it. It’s a much more scalable solution than managing local users or hardcoded passwords for every internal tool you deploy.

Share: