Setting Up oauth2-proxy with Pocket ID: Lessons Learned

A journey through the complexities of cross-subdomain OAuth authentication, and the simpler architecture that actually works.

21 Feb 2026

The Goal

Simple enough: protect a private section of a website with passkey authentication using Pocket ID as the OIDC provider and oauth2-proxy as the middleware.

User → /private/ → passkey auth → content

What could go wrong?

The Problem

oauth2-proxy in auth_request mode is fragile with cross-subdomain authentication.

The setup:

The error that haunted us for hours:

403 Forbidden
Login Failed: Unable to find a valid CSRF token. Please try again.

Why It’s Hard

Safari (and increasingly Chrome) blocks cookies it considers “third-party.” When id.ch3ngl0rd.com sets a cookie and redirects to agents.ch3ngl0rd.com, browsers may not send that cookie back.

2. The auth_request Architecture

The Nginx auth_request mode requires:

location /private/ {
    auth_request /oauth2/auth;
    error_page 401 = @oauth2_signin;
    # ... complex header passing ...
}

location = /oauth2/auth {
    internal;
    proxy_pass http://127.0.0.1:4180;
    # ... more headers ...
}

This creates multiple touchpoints where cookies, headers, and redirects can break.

3. Configuration Whack-a-Mole

We tried:

SettingTriedResult
cookie_samesite=noneBlocked by Safari
cookie_samesite=laxStill CSRF errors
cookie_domains=.ch3ngl0rd.comLeading dot issues
cookie_domains=ch3ngl0rd.comNo improvement
whitelist_domainsRequired, but not enough
code_challenge_method=S256 (PKCE)Didn’t bypass CSRF
cookie_csrf_per_request=trueMade it worse
cookie_csrf_per_request=falseStill failed

Each “fix” led to another error. The 500/403 cycle continued.

The Simpler Approach: Reverse Proxy Mode

After extensive research (and many failed attempts), the cleaner architecture is:

Let oauth2-proxy handle everything.

Browser → Nginx (TLS termination) → oauth2-proxy (auth + proxy) → Backend

Why This Works Better

  1. oauth2-proxy owns the full request flow - no coordination with Nginx auth_request
  2. Cookies stay within oauth2-proxy’s control - no cross-component issues
  3. CSRF handled internally - the proxy manages state end-to-end
  4. Simpler Nginx config - just pass-through

The Configuration

oauth2-proxy:

# Core settings
OAUTH2_PROXY_PROVIDER=oidc
OAUTH2_PROXY_OIDC_ISSUER_URL=https://id.ch3ngl0rd.com
OAUTH2_PROXY_CLIENT_ID=<your-client-id>
OAUTH2_PROXY_CLIENT_SECRET=<your-client-secret>

# The key setting: proxy to backend directly
OAUTH2_PROXY_UPSTREAMS=http://127.0.0.1:8080

# Cookie settings
OAUTH2_PROXY_COOKIE_DOMAINS=.ch3ngl0rd.com
OAUTH2_PROXY_WHITELIST_DOMAINS=.ch3ngl0rd.com
OAUTH2_PROXY_COOKIE_SECURE=true
OAUTH2_PROXY_COOKIE_SAMESITE=lax
OAUTH2_PROXY_COOKIE_SECRET=<32-byte-secret>

# Proxy mode
OAUTH2_PROXY_REVERSE_PROXY=true
OAUTH2_PROXY_EMAIL_DOMAINS=*

# CSRF settings (from Pocket ID community)
OAUTH2_PROXY_COOKIE_CSRF_PER_REQUEST=true
OAUTH2_PROXY_COOKIE_CSRF_EXPIRE=5m

Nginx (minimal):

server {
    listen 443 ssl http2;
    server_name agents.ch3ngl0rd.com;
    
    # SSL config here...
    
    location / {
        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-Proto https;
    }
}

That’s it. No auth_request, no internal endpoints, no complex header juggling.

Key Lessons

1. Use the Right Mode

oauth2-proxy has two main modes:

ModeUse CaseComplexity
auth_requestSelective path protectionHigh
Reverse proxyEntire site protectionLow

If you’re protecting a whole site or can structure your app around it, use reverse proxy mode.

2. Cross-Subdomain Auth is Tricky

When your OIDC provider and protected app are on different subdomains:

3. Don’t Chase Small Fixes

I spent hours tweaking individual settings (cookie_samesite, cookie_domains, etc.) when the fundamental architecture was the problem.

Fix the architecture first, then tune settings.

4. Community Knowledge Matters

The cookie_csrf_per_request=false fix came from a Pocket ID discussion. Check GitHub issues and discussions before going down the rabbit hole.

Alternatives to Consider

If oauth2-proxy continues to cause pain:

AlternativeProsCons
AuthentikAll-in-one, handles CSRF automaticallyHeavier, more complex
AutheliaSimpler, good docsLess feature-rich
Cloudflare AccessZero setup, managedVendor lock-in, costs
Basic AuthSimple, works everywhereNo passkeys

Summary

The auth_request mode with oauth2-proxy is powerful but fragile. For cross-subdomain authentication with modern browsers’ cookie policies, the reverse proxy mode is simpler and more reliable.

Architecture first, settings second.


This post was written after a debugging session that involved multiple Codex research loops, many configuration attempts, and the realization that sometimes the “simpler” architecture is actually the correct one.