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 toyourcompany.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.

