Mohamed Zaki
08/30/2025, 10:28 PMname: prefect-wizard
services:
nginx:
image: nginx:latest
ports:
- "80:80" # Expose NGINX on port 80
volumes:
- ./proxies/nginx.conf:/etc/nginx/nginx.conf:ro
depends_on:
- server
- api
- keycloak
- oauth2-proxy
# VNC browser for local development to use for keycloak and oauth2-proxy
browser:
image: jlesage/firefox
ports:
- "5800:5800"
environment:
# Optional: Set a password for the VNC web interface
- VNC_PASSWORD=my_password
depends_on:
- keycloak
- oauth2-proxy
- nginx
keycloak:
image: <http://quay.io/keycloak/keycloak:24.0|quay.io/keycloak/keycloak:24.0>
command: [ "start-dev", "--http-port=8080" ]
environment:
KC_BOOTSTRAP_ADMIN_USERNAME: admin
KC_BOOTSTRAP_ADMIN_PASSWORD: admin
#KC_HOSTNAME: "keycloak" # no path, just host (and optional port)
KC_HOSTNAME_STRICT: "false" # dev convenience
KC_HOSTNAME_STRICT_HTTPS: "false" # dev convenience
KC_PROXY: "edge" # recommended when behind a proxy
ports:
- "8080:8080"
volumes:
- keycloak_data:/opt/keycloak/data
- ./keycloak/local/config.json:/opt/keycloak/conf/realm-config.json
oauth2-proxy:
image: <http://quay.io/oauth2-proxy/oauth2-proxy:v7.5.1|quay.io/oauth2-proxy/oauth2-proxy:v7.5.1>
extra_hosts:
- "host.docker.internal:host-gateway"
ports:
- "4180:4180"
command: [ "--config=/etc/oauth2-proxy.cfg" ]
volumes:
- "./proxies/oauth2-proxy.cfg:/etc/oauth2-proxy.cfg"
depends_on:
- keycloak
api:
build:
context: .
dockerfile: docker/api/nonprod/Dockerfile
ports:
- "9832:9832"
environment:
PREFECT_API_URL: <http://server:4200/api>
volumes:
- ./api:/app
- ./autoupdate:/app/autoupdate
- ./core:/app/core
extra_hosts:
- "host.docker.internal:host-gateway"
env_file:
- .env
restart: always
healthcheck:
test: [ "CMD-SHELL", "curl -f <http://localhost:9832/health> || exit 1" ]
interval: 5s
timeout: 5s
retries: 5
db:
...
redis:
...
server:
image: prefecthq/prefect:3.4.13-python3.11
depends_on:
db:
condition: service_healthy
redis:
condition: service_healthy
environment:
PREFECT_API_DATABASE_CONNECTION_URL: <postgresql+asyncpg://prefect:prefect@db:5432/prefect>
PREFECT_SERVER_API_HOST: 0.0.0.0
PREFECT_UI_API_URL: "<http://localhost:4200/api>"
PREFECT_MESSAGING_BROKER: prefect_redis.messaging
PREFECT_MESSAGING_CACHE: prefect_redis.messaging
PREFECT_REDIS_MESSAGING_HOST: redis
PREFECT_REDIS_MESSAGING_PORT: 6379
PREFECT_REDIS_MESSAGING_DB: 0
PREFECT_LOGGING_LEVEL: DEBUG
PREFECT_LOGGING_LOG_PRINTS: True
PREFECT_SERVER_LOGGING_LEVEL: DEBUG
PREFECT_SERVER_LOGGING_LOG_PRINTS: True
#PREFECT_SERVER_API_AUTH_STRING: "admin:admin"
command: prefect server start --no-services
ports:
- "4200:4200"
healthcheck:
test: [ "CMD-SHELL", "python -c 'import json,urllib.request,sys; sys.exit(0 if json.loads(urllib.request.urlopen(\"<http://localhost:4200/api/health>\", timeout=1).read()) is True else 1)' " ]
interval: 5s
timeout: 3s
retries: 30
services:
...
create-deployment:
...
autoupdate-worker:
image: prefect-workpool
depends_on:
server:
condition: service_healthy
services:
condition: service_started
create-deployment:
condition: service_completed_successfully
environment:
PREFECT_API_URL: <http://server:4200/api>
DOCKER_HOST: <tcp://host.docker.internal:2375>
PREFECT_LOCAL_STORAGE_PATH: /logs/storage
# DOCKER_TLS_VERIFY: ${DOCKER_TLS_VERIFY:-}
# DOCKER_CERT_PATH: ${DOCKER_CERT_PATH:-}
restart: always
volumes:
- ./autoupdate:/opt/prefect/autoupdate
- ./core:/opt/prefect/autoupdate/core
- ${HOME}/.wizard:/logs
extra_hosts:
- "host.docker.internal:host-gateway"
env_file:
- .env
volumes:
postgres_data:
redis_data:
keycloak_data:
I will send you keycloak config, oauth-proxy configs, nginx configs on separate messages as i am not allowed to post a message that is too long
The error i am gettting:
When I try to visit http://nginx on my virtual browser, it routes me to http://keycloak:8080/realms/wizard/protocol/.... with error invalid parameter: redirect uri
Marvin
08/30/2025, 10:28 PMMarvin
08/30/2025, 10:30 PMredirect_uri
sent in the OIDC authorization request does not exactly match any value in the Keycloak client’s “Valid Redirect URIs”.
In your setup, you’re accessing via the Docker-internal hostname <http://nginx>
(from the browser
container). That means:
- oauth2-proxy must advertise its redirect URL as <http://nginx/oauth2/callback>
(since that’s what the browser sees)
- The Keycloak client for oauth2-proxy must allow that exact redirect URL
- NGINX must proxy /oauth2/*
to the oauth2-proxy service and pass X-Forwarded headers so oauth2-proxy can compute the correct external URL
Quick fix checklist:
1) In Keycloak (realm: wizard) for the oauth2-proxy client:
- Valid Redirect URIs:
- Add <http://nginx/oauth2/callback>
- If you also plan to hit via your host browser at http://localhost, add <http://localhost/oauth2/callback>
too
- Web Origins:
- Add <http://nginx>
(or +
for dev)
- Client type typically “confidential” with a client secret that matches oauth2-proxy
2) In oauth2-proxy.cfg include:
- redirect_url = "<http://nginx/oauth2/callback>"
- oidc_issuer_url = "<http://keycloak:8080/realms/wizard>"
- client_id
and client_secret
matching the Keycloak client
- reverse_proxy = true
- For HTTP dev: cookie_secure = false
- Optional but recommended for NGINX auth_request: set_xauthrequest = true
, pass_authorization_header = true
Example oauth2-proxy.cfg
provider = "oidc"
oidc_issuer_url = "<http://keycloak:8080/realms/wizard>"
client_id = "oauth2-proxy"
client_secret = "<your-client-secret>"
# Must match what the browser sees externally
redirect_url = "<http://nginx/oauth2/callback>"
email_domains = [ "*" ]
cookie_secret = "<32-byte-base64-secret>"
cookie_secure = false
cookie_samesite = "lax"
reverse_proxy = true
set_xauthrequest = true
pass_authorization_header = true
3) In NGINX, be sure you:
- Proxy /oauth2/
to oauth2-proxy:4180
- Use auth_request
to protect your upstream(s)
- Forward the X-Forwarded headers so oauth2-proxy knows the external host (nginx) and scheme
Example nginx.conf snippet
events {}
http {
upstream prefect_ui {
server server:4200;
}
upstream oauth2_proxy {
server oauth2-proxy:4180;
}
server {
listen 80;
server_name nginx;
# oauth2-proxy endpoints
location /oauth2/ {
proxy_pass <http://oauth2_proxy>;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Scheme $scheme;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Uri $request_uri;
}
# auth gate used by auth_request
location = /oauth2/auth {
proxy_pass <http://oauth2_proxy>;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Scheme $scheme;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Uri $request_uri;
}
# Protect Prefect UI (example)
location / {
auth_request /oauth2/auth;
error_page 401 = /oauth2/sign_in;
# optional: pass user info to upstream
auth_request_set $user $upstream_http_x_auth_request_user;
proxy_set_header X-User $user;
proxy_pass <http://prefect_ui>;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
}
Why this matters:Marvin
08/30/2025, 10:30 PMredirect_uri
from its --redirect-url
(or the forwarded host/scheme). If that ends up as <http://oauth2-proxy:4180/oauth2/callback>
or <http://localhost/oauth2/callback>
but your Keycloak client only allows <http://nginx/oauth2/callback>
, Keycloak will throw “invalid parameter: redirect uri”.
A couple of clarifying questions so I can tailor this exactly:
- Which hostname(s) do you plan to access from your actual browser: <http://nginx>
, <http://localhost>
, or a real domain?
- What’s your current oauth2-proxy.cfg?
- For the Keycloak client used by oauth2-proxy, what are the current “Valid Redirect URIs” and “Web Origins”?
- Could you share your nginx.conf server block(s) for the Prefect routes and /oauth2/*?
Relevant docs:
- Keycloak redirect URIs: Keycloak docs: Redirect URIs
- oauth2-proxy configuration: oauth2-proxy config overview
- oauth2-proxy with NGINX auth_request: NGINX auth_request setup
Send over your Keycloak, oauth2-proxy, and NGINX configs when you can, and I’ll validate them line-by-line.Mohamed Zaki
08/30/2025, 10:53 PMprovider = "keycloak-oidc"
oidc_issuer_url = "<http://keycloak:8080/realms/wizard>"
client_id = "wizard-oauth"
client_secret = "olcWi1lGcyrU82MtdvNgK8Nm7x4C8ieA"
cookie_secret = "bHEyY2E0dEZHSTlnY2hJa0FRWnRERURxaDg4Qm5qY3k="
email_domains = "*"
insecure_oidc_allow_unverified_email = true
http_address = "0.0.0.0:4180"
redirect_url = "<http://nginx:80/oauth2/callback>"
pass_access_token = true
skip_provider_button = true
reverse_proxy = true
nginx:
events {
worker_connections 1024;
}
http {
upstream prefect_ui {
server server:4200;
}
upstream oauth2_proxy {
server oauth2-proxy:4180;
}
upstream api {
server api:9832;
}
server {
listen 80;
server_name nginx;
# The /oauth2/ location handles all OAuth2 Proxy traffic
location /oauth2/ {
proxy_pass <http://oauth2_proxy>;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Scheme $scheme;
proxy_set_header X-Auth-Request-Redirect $request_uri;
}
# The /oauth2/auth location is the actual authentication endpoint
location = /oauth2/auth {
proxy_pass <http://oauth2_proxy>;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Scheme $scheme;
proxy_set_header Content-Length "";
proxy_pass_request_body off;
}
# The / location is for the main Prefect UI
location / {
auth_request /oauth2/auth;
error_page 401 = /oauth2/sign_in;
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;
# if you enabled --pass-access-token, this will pass the token to the backend
auth_request_set $token $upstream_http_x_auth_request_access_token;
proxy_set_header X-Access-Token $token;
proxy_pass <http://prefect_ui>;
}
...
Marvin
08/30/2025, 10:53 PMMarvin
08/30/2025, 10:55 PMprovider = "keycloak-oidc"
oidc_issuer_url = "<http://keycloak:8080/realms/wizard>"
client_id = "wizard-oauth"
client_secret = "olcWi1lGcyrU82MtdvNgK8Nm7x4C8ieA"
redirect_url = "<http://nginx/oauth2/callback>" # (port 80 implied)
reverse_proxy = true
email_domains = "*"
insecure_oidc_allow_unverified_email = true
pass_access_token = true
cookie_secret = "bHEyY2E0dEZHSTlnY2hJa0FRWnRERURxaDg4Qm5qY3k="
cookie_secure = false # IMPORTANT for HTTP; otherwise the cookie won’t be sent
cookie_samesite = "lax"
set_xauthrequest = true
pass_authorization_header = true
2) Strengthen NGINX forwarded headers
- Ensure oauth2-proxy sees the external host/scheme so it sets cookies for the right host and computes redirects correctly
- Also set X-Auth-Request-Redirect on the /oauth2/auth location
server {
listen 80;
server_name nginx;
location /oauth2/ {
proxy_pass <http://oauth2_proxy>;
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-Forwarded-Host $host;
proxy_set_header X-Forwarded-Uri $request_uri;
}
location = /oauth2/auth {
proxy_pass <http://oauth2_proxy>;
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-Forwarded-Host $host;
proxy_set_header X-Forwarded-Uri $request_uri;
proxy_set_header Content-Length "";
proxy_pass_request_body off;
proxy_set_header X-Auth-Request-Redirect $request_uri;
}
location / {
auth_request /oauth2/auth;
error_page 401 = /oauth2/sign_in;
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;
auth_request_set $token $upstream_http_x_auth_request_access_token;
proxy_set_header X-Access-Token $token;
proxy_pass <http://prefect_ui>;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host;
}
}
Then:
- Clear cookies for the nginx host in your VNC browser
- Hit http://nginx/oauth2/sign_in, login, and verify you no longer hit the 403
If it still fails, please:
- Check in the browser devtools if a CSRF cookie from oauth2-proxy is ever set for host nginx before the redirect (name often like _oauth2_proxy_csrf or __Host-oauth2-proxy-csrf)
- Share the oauth2-proxy logs around the login; we can turn up logging if needed
References:
- oauth2-proxy CSRF troubleshooting: Unable to find a valid CSRF token
- oauth2-proxy config overview: Configuration overview
Once auth is working, we can harden it (cookie_expire, refresh, AllowedGroups from Keycloak, etc.) and make sure Prefect UI/API routes and websockets behave nicely behind NGINX.Mohamed Zaki
08/30/2025, 11:01 PMMarvin
08/30/2025, 11:01 PMMarvin
08/30/2025, 11:01 PM# existing settings...
provider = "keycloak-oidc"
oidc_issuer_url = "<http://keycloak:8080/realms/wizard>"
client_id = "wizard-oauth"
client_secret = "REDACTED"
redirect_url = "<http://nginx/oauth2/callback>"
reverse_proxy = true
email_domains = [ "*" ]
insecure_oidc_allow_unverified_email = true
pass_access_token = true
cookie_secret = "REDACTED"
cookie_secure = false
cookie_samesite = "lax"
set_xauthrequest = true
pass_authorization_header = true
# KEY FIX:
oidc_audience_claim = "azp"
Option B: Make Keycloak include your client as an audience
- In Keycloak Admin:
- Clients > wizard-oauth > Client scopes (or Mappers)
- Create mapper: “Audience”
- Included Client Audience: wizard-oauth
- Add to access token: ON
- Add to ID token: ON
- This ensures aud includes wizard-oauth, satisfying oauth2-proxy’s default behavior.
After applying either fix
- Clear cookies for the nginx host in your VNC Firefox
- Try again via http://nginx/
Quick checks
- Decode the ID token you receive from Keycloak and confirm:
- azp == wizard-oauth
- aud contains wizard-oauth (if you applied Option B)
- Ensure oauth2-proxy still sees the correct external host/scheme via the NGINX forwarded headers you set earlier.
Security note: you posted a client_secret in the thread—rotate it in Keycloak and update your oauth2-proxy config.
Useful docs
- oauth2-proxy OIDC options (incl. oidc_audience_claim): oauth2-proxy configuration overview
- Keycloak audience mapping (concept): Keycloak docs: Audience
If it still errors, share:
- A redacted decoded ID token’s header/payload (remove sig), so we can verify aud/azp
- The exact oauth2-proxy log line after your changeMohamed Zaki
08/30/2025, 11:16 PM172.20.0.10 - 883f9412-b5fd-4dc1-956c-c3f1117ae167 - test@test.com [2025/08/30 23:14:15] [AuthSuccess] Authenticated via OAuth2: Session{email:test@test.com user:f6246d8d-cade-450b-ba3d-74ca051da430 PreferredUsername:test token:true id_token:true created:2025-08-30 23:14:15.378336396 +0000 UTC m=+42.703549616 expires:2025-08-30 23:19:15.366800858 +0000 UTC m=+342.692014082 refresh_token:true groups:[role:default-roles-wizard role:offline_access role:uma_authorization role:account:manage-account role:account:manage-account-links role:account:view-profile]}
172.20.0.10 - 883f9412-b5fd-4dc1-956c-c3f1117ae167 - - [2025/08/30 23:14:15] nginx GET - "/oauth2/callback?state=K3JplFChZU1FUmKwVJvtvPrnwNi0ffkPsenrFm8_Urc%3A%2F&session_state=1e14678d-c3f7-4311-87cf-955bad92ce45&iss=http%3A%2F%2Fkeycloak%3A8080%2Frealms%2Fwizard&code=f94e23eb-83ae-4346-945b-f3c22f965b63.1e14678d-c3f7-4311-87cf-955bad92ce45.e954fcbe-a31c-41ee-8c6b-da7f80aafa50" HTTP/1.0 "Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0" 302 24 0.026
Nginx however gives 502 bad gateway!
Here's nginx configs:
# See reference:
# <https://github.com/pasha-r/oauth2_proxy/blob/master/docs/configuration/configuration.md#config-file>
events {
worker_connections 1024;
}
http {
upstream prefect_ui {
server server:4200;
}
upstream oauth2_proxy {
server oauth2-proxy:4180;
}
upstream api {
server api:9832;
}
server {
listen 80;
server_name nginx;
# The /oauth2/ location handles all OAuth2 Proxy traffic
location /oauth2/ {
proxy_pass <http://oauth2_proxy>;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Scheme $scheme;
proxy_set_header X-Auth-Request-Redirect $request_uri;
}
# The /oauth2/auth location is the actual authentication endpoint
location = /oauth2/auth {
proxy_pass <http://oauth2_proxy>;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Scheme $scheme;
proxy_set_header Content-Length "";
proxy_pass_request_body off;
}
# The / location is for the main Prefect UI
location / {
auth_request /oauth2/auth;
error_page 401 = /oauth2/sign_in;
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;
# if you enabled --pass-access-token, this will pass the token to the backend
auth_request_set $token $upstream_http_x_auth_request_access_token;
proxy_set_header X-Access-Token $token;
proxy_pass <http://prefect_ui>;
}
# The /api/ location is for the API, also protected by the proxy
location /wizardous/ {
auth_request /oauth2/auth;
error_page 401 = /oauth2/sign_in;
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://api>;
}
# Skip favicon.ico
location = /favicon.ico {
return 204;
}
}
}
Marvin
08/30/2025, 11:16 PMMarvin
08/30/2025, 11:19 PMMarvin
08/30/2025, 11:19 PM3) Fix Prefect Server UI’s API URL
Right now your server has PREFECT_UI_API_URL="<http://localhost:4200/api>" which is wrong for traffic coming via NGINX “nginx”. Change it to the external URL the browser actually uses. Since your browser container reaches the site at <http://nginx>:
- In the server service:
- PREFECT_UI_API_URL=<http://nginx/api>
Then restart the stack.
Also add in oauth2-proxy (for auth_request headers you’re reading in nginx):
set_xauthrequest = true
pass_authorization_header = true
```
If you still see 502:
- Share the NGINX error lines (they’re very specific), e.g. “connect() failed (111: Connection refused) while connecting to upstream” or “upstream prematurely closed connection”.
- From the nginx container, show the outputs of:
- getent hosts server
- printf 'GET /api/health HTTP/1.1\r\nHost: server\r\n\r\n' | nc -v server 4200
Once we get a clean 200 from / and /api/health through NGINX, we can finalize the UI/API URLs and then harden cookies and group-based access.
References:
- Example Prefect behind reverse proxy (with websockets/SSE): Example: Prefect + reverse proxy
- oauth2-proxy config overview: oauth2-proxy configurationMohamed Zaki
08/30/2025, 11:24 PM172.20.0.10 - - [30/Aug/2025:23:17:17 +0000] "GET /oauth2/callback?state=6EnzKVrJ2flouQolVuBT4pdgl_1MmU7o1IjFnm2vwHY%3A%2Foauth2%2Fcallback?&session_state=1e14678d-c3f7-4311-87cf-955bad92ce45&iss=http%3A%2F%2Fkeycloak%3A8080%2Frealms%2Fwizard&code=79de3057-346e-44c4-bd15-483ab30ab89c.1e14678d-c3f7-4311-87cf-955bad92ce45.e954fcbe-a31c-41ee-8c6b-da7f80aafa50 HTTP/1.1" 502 157 "-" "Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0"
2025/08/30 23:17:17 [error] 22#22: *26 upstream sent too big header while reading response header from upstream, client: 172.20.0.10, server: nginx, request: "GET /oauth2/callback?state=6EnzKVrJ2flouQolVuBT4pdgl_1MmU7o1IjFnm2vwHY%3A%2Foauth2%2Fcallback?&session_state=1e14678d-c3f7-4311-87cf-955bad92ce45&iss=http%3A%2F%2Fkeycloak%3A8080%2Frealms%2Fwizard&code=79de3057-346e-44c4-bd15-483ab30ab89c.1e14678d-c3f7-4311-87cf-955bad92ce45.e954fcbe-a31c-41ee-8c6b-da7f80aafa50 HTTP/1.1", upstream: "<http://172.20.0.8:4180/oauth2/callback?state=6EnzKVrJ2flouQolVuBT4pdgl_1MmU7o1IjFnm2vwHY%3A%2Foauth2%2Fcallback?&session_state=1e14678d-c3f7-4311-87cf-955bad92ce45&iss=http%3A%2F%2Fkeycloak%3A8080%2Frealms%2Fwizard&code=79de3057-346e-44c4-bd15-483ab30ab89c.1e14678d-c3f7-4311-87cf-955bad92ce45.e954fcbe-a31c-41ee-8c6b-da7f80aafa50>", host: "nginx"
172.20.0.10 - - [30/Aug/2025:23:17:17 +0000] "GET /favicon.ico HTTP/1.1" 204 0 "<http://nginx/oauth2/callback?state=6EnzKVrJ2flouQolVuBT4pdgl_1MmU7o1IjFnm2vwHY%3A%2Foauth2%2Fcallback?&session_state=1e14678d-c3f7-4311-87cf-955bad92ce45&iss=http%3A%2F%2Fkeycloak%3A8080%2Frealms%2Fwizard&code=79de3057-346e-44c4-bd15-483ab30ab89c.1e14678d-c3f7-4311-87cf-955bad92ce45.e954fcbe-a31c-41ee-8c6b-da7f80aafa50>" "Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0"
172.20.0.10 - - [30/Aug/2025:23:22:04 +0000] "GET /favicon.ico HTTP/1.1" 204 0 "<http://nginx/oauth2/callback?state=6EnzKVrJ2flouQolVuBT4pdgl_1MmU7o1IjFnm2vwHY%3A%2Foauth2%2Fcallback?&session_state=1e14678d-c3f7-4311-87cf-955bad92ce45&iss=http%3A%2F%2Fkeycloak%3A8080%2Frealms%2Fwizard&code=79de3057-346e-44c4-bd15-483ab30ab89c.1e14678d-c3f7-4311-87cf-955bad92ce45.e954fcbe-a31c-41ee-8c6b-da7f80aafa50>" "Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0"
172.20.0.10 - - [30/Aug/2025:23:22:05 +0000] "GET /oauth2/callback? HTTP/1.1" 500 2815 "<http://nginx/oauth2/callback?state=K3JplFChZU1FUmKwVJvtvPrnwNi0ffkPsenrFm8_Urc%3A%2F&session_state=1e14678d-c3f7-4311-87cf-955bad92ce45&iss=http%3A%2F%2Fkeycloak%3A8080%2Frealms%2Fwizard&code=f94e23eb-83ae-4346-945b-f3c22f965b63.1e14678d-c3f7-4311-87cf-955bad92ce45.e954fcbe-a31c-41ee-8c6b-da7f80aafa50>" "Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0"
172.20.0.10 - - [30/Aug/2025:23:22:05 +0000] "GET /oauth2/static/css/bulma.min.css HTTP/1.1" 200 207302 "<http://nginx/oauth2/callback>?" "Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0"
172.20.0.10 - - [30/Aug/2025:23:22:05 +0000] "GET /oauth2/static/css/all.min.css HTTP/1.1" 200 102025 "<http://nginx/oauth2/callback>?" "Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0"
172.20.0.10 - - [30/Aug/2025:23:22:05 +0000] "GET /favicon.ico HTTP/1.1" 204 0 "<http://nginx/oauth2/callback>?" "Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0"
172.20.0.10 - - [30/Aug/2025:23:22:06 +0000] "GET /oauth2/static/webfonts/fa-solid-900.woff2 HTTP/1.1" 200 149908 "<http://nginx/oauth2/static/css/all.min.css>" "Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0"
Marvin
08/30/2025, 11:24 PMMarvin
08/30/2025, 11:25 PMhttp {
# ... existing ...
# Bigger buffers for large Set-Cookie headers from oauth2-proxy
proxy_buffer_size 64k;
proxy_buffers 16 64k;
proxy_busy_buffers_size 128k;
server {
listen 80;
server_name nginx;
location /oauth2/ {
proxy_pass <http://oauth2_proxy>;
proxy_http_version 1.1;
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-Forwarded-Host $host;
proxy_set_header X-Forwarded-Uri $request_uri;
proxy_set_header X-Auth-Request-Redirect $request_uri;
# Important: buffer sizes for big Set-Cookie
proxy_buffer_size 64k;
proxy_buffers 16 64k;
proxy_busy_buffers_size 128k;
}
location = /oauth2/auth {
proxy_pass <http://oauth2_proxy>;
proxy_http_version 1.1;
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-Forwarded-Host $host;
proxy_set_header X-Forwarded-Uri $request_uri;
proxy_set_header Content-Length "";
proxy_pass_request_body off;
# Same buffering
proxy_buffer_size 64k;
proxy_buffers 16 64k;
proxy_busy_buffers_size 128k;
}
# keep your other locations as before (add Upgrade/Connection if you haven’t yet)
}
}
B) Better long-term: use oauth2-proxy Redis session store (small cookies)
- Instead of stuffing tokens into a cookie, store session server-side in Redis. You already run Redis; use a separate DB index to avoid conflicts with Prefect (e.g., DB 2).
Add to oauth2-proxy.cfg:
# Keep what you have, plus:
session_store_type = "redis"
redis_connection_url = "<redis://redis:6379/2>" # use a different DB than Prefect (Prefect uses 0)
# optional tuning
cookie_expire = "8h"
cookie_refresh = "1h"
Then restart oauth2-proxy. This typically shrinks Set-Cookie drastically and avoids having to inflate buffers.
Also double-check these from earlier:
- oauth2-proxy.cfg should include
cookie_secure = false
cookie_samesite = "lax"
set_xauthrequest = true
pass_authorization_header = true
- Your Prefect server env should point the UI to the API via NGINX, not localhost:
PREFECT_UI_API_URL=<http://nginx/api>
(You currently have http://localhost:4200/api; change it and restart the server.)
Validate quickly:
- From the nginx container:
- curl -I http://oauth2-proxy:4180/oauth2/sign_in
- curl -I http://server:4200/api/health
- Try login again; the /oauth2/callback should 302 to “/” without 502.
If you still see errors, please share:
- Any new NGINX error lines after the buffer changes
- The response headers size from oauth2-proxy (curl -sv http://oauth2-proxy:4180/oauth2/sign_in | sed -n '1,/^$/p') to confirm header magnitude
Docs for reference:
- NGINX proxy buffering: proxy_buffer_size and proxy_buffers
- oauth2-proxy session stores: Session storageMohamed Zaki
08/30/2025, 11:54 PM# See reference:
# <https://github.com/pasha-r/oauth2_proxy/blob/master/docs/configuration/configuration.md#config-file>
events {
worker_connections 1024;
}
http {
# Bigger buffers for large Set-Cookie headers from oauth2-proxy
proxy_buffer_size 64k;
proxy_buffers 16 64k;
proxy_busy_buffers_size 128k;
upstream prefect_ui {
server server:4200;
}
upstream oauth2_proxy {
server oauth2-proxy:4180;
}
upstream api {
server api:9832;
}
server {
listen 80;
server_name nginx;
# The /oauth2/ location handles all OAuth2 Proxy traffic
location /oauth2/ {
proxy_pass <http://oauth2_proxy>;
proxy_http_version 1.1;
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-Forwarded-Host $host;
proxy_set_header X-Forwarded-Uri $request_uri;
proxy_set_header X-Auth-Request-Redirect $request_uri;
# Important: buffer sizes for big Set-Cookie
proxy_buffer_size 64k;
proxy_buffers 16 64k;
proxy_busy_buffers_size 128k;
}
# The /oauth2/auth location is the actual authentication endpoint
location = /oauth2/auth {
proxy_pass <http://oauth2_proxy>;
proxy_http_version 1.1;
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-Forwarded-Host $host;
proxy_set_header X-Forwarded-Uri $request_uri;
proxy_set_header Content-Length "";
proxy_pass_request_body off;
# Same buffering
proxy_buffer_size 64k;
proxy_buffers 16 64k;
proxy_busy_buffers_size 128k;
}
# The / location is for the main Prefect UI
location / {
auth_request /oauth2/auth;
error_page 401 = /oauth2/sign_in;
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;
# if you enabled --pass-access-token, this will pass the token to the backend
auth_request_set $token $upstream_http_x_auth_request_access_token;
proxy_set_header X-Access-Token $token;
proxy_pass <http://prefect_ui>;
}
# The /api/ location is for the API, also protected by the proxy
location /wizardous/ {
auth_request /oauth2/auth;
error_page 401 = /oauth2/sign_in;
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;
rewrite ^/wizardous/(.*) /$1 break;
proxy_pass <http://api>;
}
# # Skip favicon.ico
# location = /favicon.ico {
# return 204;
# }
}
}
/wizardous/* routes to an API, so it shouldn't be accessed via a login page, it should use tokens instead, how do I make that happen and how do I generate tokens per userMarvin
08/30/2025, 11:54 PMMarvin
08/30/2025, 11:56 PMset_xauthrequest = true
pass_authorization_header = true
pass_access_token = true
2) Ensure /oauth2/auth gets the Authorization header
Add this to your NGINX /oauth2/auth location so Bearer tokens sent by clients reach oauth2-proxy:
proxy_set_header Authorization $http_authorization;
3) Make /wizardous/ return 401 (no redirect) and inject Bearer to your API
Replace your /wizardous/ block with:
location /wizardous/ {
# Validate via oauth2-proxy, but DO NOT redirect to login
auth_request /oauth2/auth;
# If not authenticated, return a JSON 401 instead of redirecting
error_page 401 = @api_unauthorized;
# Optional: forward user info
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;
# If authenticated with oauth2-proxy, inject a Bearer token for your API
auth_request_set $token $upstream_http_x_auth_request_access_token;
proxy_set_header Authorization "Bearer $token";
# Preserve Authorization if client already sent its own token
if ($http_authorization) {
proxy_set_header Authorization $http_authorization;
}
rewrite ^/wizardous/(.*)$ /$1 break;
proxy_pass <http://api>;
}
location @api_unauthorized {
add_header WWW-Authenticate 'Bearer realm="wizard", error="invalid_token"' always;
default_type application/json;
return 401 '{"error":"unauthorized"}';
}
Notes:
- For browser-originating calls, oauth2-proxy issues the access token; NGINX injects it so your API sees Authorization: Bearer ....
- For programmatic clients, they can call /wizardous/* with their own Bearer token; with the proxy_set_header Authorization $http_authorization;
line, that token is forwarded to the API.
- If you want oauth2-proxy to accept and validate inbound Bearer tokens itself (so only valid tokens get through), you can enable its JWT validation mode (commonly via “skip_jwt_bearer_tokens” and issuer/audience settings). If you prefer, let your API validate instead (see option B below).
Option B: Let your API validate JWTs and skip oauth2-proxy for /wizardous/
Use this simpler NGINX block and perform JWT validation in your API:
location /wizardous/ {
# No oauth2_proxy here
proxy_set_header Authorization $http_authorization;
rewrite ^/wizardous/(.*)$ /$1 break;
proxy_pass <http://api>;
}
In your API, validate tokens against Keycloak:
- Issuer: http://keycloak:8080/realms/wizard
- JWKS: http://keycloak:8080/realms/wizard/protocol/openid-connect/certs
- Validate aud to include your API client_id, and exp/nbf, etc.
How to get “tokens per user”
- Browser users (your UI): They authenticate via oauth2-proxy (Auth Code flow). You don’t need to hand tokens to the browser app if you use the injection method above; NGINX injects the Bearer for the API calls. If you do need the token in JS, you’ll need a different architecture (SPA with PKCE) that talks directly to Keycloak.
- CLI or scripts (user tokens):Marvin
08/30/2025, 11:56 PMcurl -s -X POST "<http://keycloak:8080/realms/wizard/protocol/openid-connect/device/auth>" \
-d "client_id=wizard-cli" \
-d "scope=openid profile email offline_access"
2) User visits the verification_uri_complete from the response and logs in
3) Poll for tokens
curl -s -X POST "<http://keycloak:8080/realms/wizard/protocol/openid-connect/token>" \
-d "grant_type=urn:ietf:params:oauth:grant-type:device_code" \
-d "client_id=wizard-cli" \
-d "device_code=...from step 1..."
- Resource Owner Password (Direct Access Grants) — enable on the client; not recommended for prod
curl -s -X POST "<http://keycloak:8080/realms/wizard/protocol/openid-connect/token>" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "client_id=wizard-cli" \
-d "client_secret=REDACTED" \
-d "grant_type=password" \
-d "username=test@test.com" \
-d "password=YOUR_PASSWORD"
- Service-to-service (no user): Client Credentials grant
curl -s -X POST "<http://keycloak:8080/realms/wizard/protocol/openid-connect/token>" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "client_id=wizard-api" \
-d "client_secret=REDACTED" \
-d "grant_type=client_credentials"
Configure clients in Keycloak accordingly (public + PKCE for SPAs, confidential for CLI/machine clients). Make sure the audience of issued tokens includes your API’s client_id (you already added an audience mapper earlier).
Docs you may find helpful:
- oauth2-proxy + NGINX auth_request and headers: oauth2-proxy: NGINX auth_request
- oauth2-proxy session and headers: oauth2-proxy configuration
- Keycloak device flow: Keycloak: Device Authorization Grant
If you tell me whether you want oauth2-proxy to validate inbound Bearer tokens or you prefer to validate inside your API, I’ll tailor the exact config (including issuer/audience options) and provide a quick token verification snippet for your API stack.Mohamed Zaki
08/31/2025, 12:08 AMMarvin
08/31/2025, 12:08 AMMarvin
08/31/2025, 12:10 AM# Keep your OIDC settings for UI login flows
provider = "keycloak-oidc"
oidc_issuer_url = "<http://keycloak:8080/realms/wizard>"
client_id = "wizard-oauth"
client_secret = "REDACTED"
redirect_url = "<http://nginx/oauth2/callback>"
reverse_proxy = true
# Session/cookie settings (for UI)
cookie_secret = "REDACTED"
cookie_secure = false
cookie_samesite = "lax"
set_xauthrequest = true
pass_authorization_header = true
# IMPORTANT: accept & validate incoming JWT bearer tokens for the API
skip_jwt_bearer_tokens = true
extra_jwt_issuers = [ "<http://keycloak:8080/realms/wizard=wizard-api>" ]
# Optional (handy for CORS preflight to the API)
skip_auth_preflight = true
Notes:
- extra_jwt_issuers tells oauth2-proxy to validate JWTs whose iss matches your realm and whose aud includes wizard-api.
- You’ll need tokens whose aud contains wizard-api. See Keycloak steps below.
2) NGINX: protect /wizardous/ with auth_request (no redirect), pass through Authorization
Replace your /wizardous/ block with:
location /wizardous/ {
# Validate via oauth2-proxy using the client's Bearer token
auth_request /oauth2/auth;
# Send the Authorization header to oauth2-proxy for validation
proxy_set_header Authorization $http_authorization;
# On auth failure, return 401 JSON instead of redirecting to login
error_page 401 = @api_unauthorized;
# Optional: forward some identity to your API (not required)
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;
# Forward the same Authorization header to your API untouched
proxy_set_header Authorization $http_authorization;
# Keep your path rewrite
rewrite ^/wizardous/(.*)$ /$1 break;
proxy_pass <http://api>;
proxy_http_version 1.1;
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-Forwarded-Host $host;
}
location = /oauth2/auth {
proxy_pass <http://oauth2_proxy>;
proxy_http_version 1.1;
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-Forwarded-Host $host;
proxy_set_header X-Forwarded-Uri $request_uri;
proxy_set_header Content-Length "";
proxy_pass_request_body off;
# CRITICAL: forward inbound Authorization header to oauth2-proxy for validation
proxy_set_header Authorization $http_authorization;
}
location @api_unauthorized {
add_header WWW-Authenticate 'Bearer realm="wizard", error="invalid_token"' always;
default_type application/json;
return 401 '{"error":"unauthorized"}';
}
- Result: If a request lacks a valid Bearer JWT, oauth2-proxy returns 401 to the subrequest and NGINX returns the JSON 401. No login redirect.
3) Keycloak: issue tokens your edge will accept
Create a separate “API” client (recommended):
- Client: wizard-api (confidential)
- Access type: Confidential (with client secret)
- Service Accounts: enable if you want client-credentials tokens
- Add Audience mapper so aud includes wizard-api:
- Clients > wizard-api > Client scopes or Mappers > Create > Mapper type: Audience
- Included Client Audience: wizard-apiMarvin
08/31/2025, 12:10 AMcurl -s -X POST "<http://keycloak:8080/realms/wizard/protocol/openid-connect/token>" \
-d "grant_type=client_credentials" \
-d "client_id=wizard-api" \
-d "client_secret=REDACTED"
- Per-user tokens for CLI/automation (Device Authorization Grant)
# 1) Start device flow
curl -s -X POST "<http://keycloak:8080/realms/wizard/protocol/openid-connect/device/auth>" \
-d "client_id=wizard-api" \
-d "scope=openid profile email offline_access"
# Follow verification_uri_complete, then:
# 2) Poll for token
curl -s -X POST "<http://keycloak:8080/realms/wizard/protocol/openid-connect/token>" \
-d "grant_type=urn:ietf:params:oauth:grant-type:device_code" \
-d "client_id=wizard-api" \
-d "device_code=...from step 1..."
- Password grant (Direct Access Grants) for dev only
curl -s -X POST "<http://keycloak:8080/realms/wizard/protocol/openid-connect/token>" \
-d "grant_type=password" \
-d "client_id=wizard-api" \
-d "client_secret=REDACTED" \
-d "username=test@test.com" \
-d "password=REDACTED"
4) Test the edge-only auth
- No token:
- curl -i http://nginx/wizardous/anything
- Expect: HTTP/1.1 401 with JSON body {"error":"unauthorized"}
- Bad token:
- curl -i -H "Authorization: Bearer xyz" http://nginx/wizardous/anything
- Expect: 401
- Valid token (aud contains wizard-api):
- TOKEN=$(curl -s ... | jq -r .access_token)
- curl -i -H "Authorization: Bearer $TOKEN" http://nginx/wizardous/anything
- Expect: 200 from your API
Optional hardening
- Restrict accepted audiences to wizard-api only (as configured)
- If your API doesn’t need to see the token, you can strip it and only pass identity headers (X-User/X-Email). If it does, keep forwarding Authorization as shown.
- For CORS: with skip_auth_preflight=true, OPTIONS preflights will pass; add Access-Control-* headers in NGINX or your API if you’re calling from browsers.
If you want me to tailor the Keycloak client settings (confidential vs public with PKCE, token lifetimes, groups-to-claims) or to add group-based enforcement at NGINX (e.g., only allow tokens with a specific claim), say the word and I’ll provide the exact mappers and NGINX rules.Mohamed Zaki
08/31/2025, 12:45 AM# curl -i <http://nginx/wizardous/health> -H "Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJxTC1ESGpnUWQyd2FLcHZZaEdRZjBTaU5YOXVNTGNQNmNNWEgwTG8tZ0FrIn0.eyJleHAiOjE3NTY2MDExOTMsImlhdCI6MTc1NjYwMDg5MywianRpIjoiZjA5ZTA2MTctNDdlZS00NDk3LTk1ODEtNTExYTcyMzhhMmRhIiwiaXNzIjoiaHR0cDovL2tleWNsb2FrOjgwODAvcmVhbG1zL3dpemFyZCIsImF1ZCI6WyJ3aXphcmQtYXBpIiwiYWNjb3VudCJdLCJzdWIiOiJiYmJmNTU2Zi0wMWMxLTRmNjMtYmY0ZC00M2NmY2Q2MGExYjYiLCJ0eXAiOiJCZWFyZXIiLCJhenAiOiJ3aXphcmQtYXBpIiwiYWNyIjoiMSIsImFsbG93ZWQtb3JpZ2lucyI6WyJodHRwOi8vbmdpbngiXSwicmVhbG1fYWNjZXNzIjp7InJvbGVzIjpbImRlZmF1bHQtcm9sZXMtd2l6YXJkIiwib2ZmbGluZV9hY2Nlc3MiLCJ1bWFfYXV0aG9yaXphdGlvbiJdfSwicmVzb3VyY2VfYWNjZXNzIjp7ImFjY291bnQiOnsicm9sZXMiOlsibWFuYWdlLWFjY291bnQiLCJtYW5hZ2UtYWNjb3VudC1saW5rcyIsInZpZXctcHJvZmlsZSJdfX0sInNjb3BlIjoicHJvZmlsZSBlbWFpbCIsImNsaWVudEhvc3QiOiIxNzIuMjAuMC42IiwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJzZXJ2aWNlLWFjY291bnQtd2l6YXJkLWFwaSIsImNsaWVudEFkZHJlc3MiOiIxNzIuMjAuMC42IiwiY2xpZW50X2lkIjoid2l6YXJkLWFwaSJ9.BhznASo2uubvFdgrOhPwu8Z4O2hViMV07jr7vdhFxNjibxKmk_9o7XgPkz7AfLtv5nBlVsBXmUBI4l2LdczPfTVkk210AIIgMm0y5u0PYqGM6EcPmU2a2Trh8uXF3-OZNLqa2YrwGxVaTTIyrMGBfxpcrw9aEC2gEPR-8wYFclnv7xbBEdKTEkwvSPWxyt6sn9y4RyiSlN2ZW9hfFp7HHrXobxxOPq6468wbPeOnZ6g4wjxASQ7T292BwCaIaRwhHHHjfIX9WCsoIXPS7twoRQTfOjSVE_DdjElzin-32js8Mt0cT7u7HPuQcZhk6lJTs0m1T0V8BLsnTJeQsSnx3A"
HTTP/1.1 302 Found
Server: nginx/1.29.1
Date: Sun, 31 Aug 2025 00:44:34 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 382
Connection: keep-alive
Cache-Control: no-cache, no-store, must-revalidate, max-age=0
Expires: Thu, 01 Jan 1970 00:00:00 UTC
Location: <http://keycloak:8080/realms/wizard/protocol/openid-connect/auth?approval_prompt=force&client_id=wizard-oauth&nonce=ycSfxjoWjEuXEpTP3Pbi92JHoQSISohcud_ePhTRqHc&redirect_uri=http%3A%2F%2Fnginx%3A80%2Foauth2%2Fcallback&response_type=code&scope=openid+email+profile&state=DL03Zzbb66tZVYQJNNHtKRGsL_tMQYCw5DFgtF4-roo%3A%2Fwizardous%2Fhealth>
Set-Cookie: _oauth2_proxy_csrf=ZTKV3QqJaZuMbAgZo4srLCPO9D1p7ifRUXl_VpoiASJN__19jBXdAQFRxLVHQWW4TYjr7wKBG6jGz0_9ALVSaW1lkqfLgt1SN51IPGwRRGmFEBiE53Z6GcM=|1756601074|T2DkBNZ9_fV2t0Zjvw3fU2dmjffKx3-esz1KlUQQ6vY=; Path=/; Expires=Sun, 31 Aug 2025 00:59:34 GMT; HttpOnly; SameSite=Lax
<a href="<http://keycloak:8080/realms/wizard/protocol/openid-connect/auth?approval_prompt=force&client_id=wizard-oauth&nonce=ycSfxjoWjEuXEpTP3Pbi92JHoQSISohcud_ePhTRqHc&redirect_uri=http%3A%2F%2Fnginx%3A80%2Foauth2%2Fcallback&response_type=code&scope=openid+email+profile&state=DL03Zzbb66tZVYQJNNHtKRGsL_tMQYCw5DFgtF4-roo%3A%2Fwizardous%2Fhealth>">Found</a>.
Marvin
08/31/2025, 12:45 AMMarvin
08/31/2025, 12:47 AM# keep your existing OIDC settings (issuer, client_id, etc.)
# Accept & validate incoming JWT bearer tokens
skip_jwt_bearer_tokens = true
extra_jwt_issuers = [ "<http://keycloak:8080/realms/wizard=wizard-api>" ]
# Nice-to-have for browser CORS preflights
skip_auth_preflight = true
# Ensure these are present for UI + nginx auth_request integration
set_xauthrequest = true
pass_authorization_header = true
pass_access_token = true
Notes:
- extra_jwt_issuers format is "ISSUER=AUDIENCE". Your sample token’s iss is http://keycloak:8080/realms/wizard and aud includes wizard-api, so this matches.
- Docs: oauth2-proxy config and skip_jwt_bearer_tokens
2) Update NGINX to pass Authorization to oauth2-proxy and return 401 JSON for API
- Replace your /wizardous/ and /oauth2/auth blocks with the following:
# Subrequest endpoint used by auth_request
location = /oauth2/auth {
proxy_pass <http://oauth2_proxy>;
proxy_http_version 1.1;
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-Forwarded-Host $host;
proxy_set_header X-Forwarded-Uri $request_uri;
# CRITICAL: forward Authorization header so oauth2-proxy can validate Bearer JWTs
proxy_set_header Authorization $http_authorization;
proxy_set_header Content-Length "";
proxy_pass_request_body off;
}
# Your API — validate token at the edge, never redirect
location /wizardous/ {
auth_request /oauth2/auth;
# On auth failure, return JSON 401 (no login redirect)
error_page 401 = @api_unauthorized;
# Forward identity (optional)
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;
# Forward the same Authorization header to your API (or drop it if you don’t want API to see it)
proxy_set_header Authorization $http_authorization;
# Keep your path rewrite
rewrite ^/wizardous/(.*)$ /$1 break;
proxy_pass <http://api>;
proxy_http_version 1.1;
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-Forwarded-Host $host;
}
location @api_unauthorized {
add_header WWW-Authenticate 'Bearer realm="wizard", error="invalid_token"' always;
default_type application/json;
return 401 '{"error":"unauthorized"}';
}
Key points:
- We removed error_page 401 = /oauth2/sign_in from the API block. That was causing your redirect.
- We forward Authorization to the auth subrequest; without this, oauth2-proxy won’t see the Bearer token and will try to start a login.
3) Ensure tokens your clients use have the right audience
- Your posted token has aud ["wizard-api","account"] and iss http://keycloak:8080/realms/wizard — perfect for the extra_jwt_issuers setting above.
- In Keycloak, make sure the “wizard-api” client has an Audience mapper that adds wizard-api to the access token’s aud.Marvin
08/31/2025, 12:47 AMcurl -s -X POST "<http://keycloak:8080/realms/wizard/protocol/openid-connect/token>" \
-d "grant_type=client_credentials" \
-d "client_id=wizard-api" \
-d "client_secret=REDACTED"
- Device flow (per-user, good for CLIs):
# Start device flow
curl -s -X POST "<http://keycloak:8080/realms/wizard/protocol/openid-connect/device/auth>" \
-d "client_id=wizard-api" \
-d "scope=openid profile email offline_access"
# Poll for token after user verifies
curl -s -X POST "<http://keycloak:8080/realms/wizard/protocol/openid-connect/token>" \
-d "grant_type=urn:ietf:params:oauth:grant-type:device_code" \
-d "client_id=wizard-api" \
-d "device_code=...from previous step..."
- Direct Access Grants (dev only):
curl -s -X POST "<http://keycloak:8080/realms/wizard/protocol/openid-connect/token>" \
-d "grant_type=password" \
-d "client_id=wizard-api" \
-d "client_secret=REDACTED" \
-d "username=test@test.com" \
-d "password=REDACTED"
4) Test it
- No token:
- curl -i http://nginx/wizardous/health
- Expect 401 with JSON {"error":"unauthorized"}
- Bad token:
- curl -i -H "Authorization: Bearer xyz" http://nginx/wizardous/health
- Expect 401
- Valid token (aud includes wizard-api):
- TOKEN=$(curl -s ... | jq -r .access_token)
- curl -i -H "Authorization: Bearer $TOKEN" http://nginx/wizardous/health
- Expect 200 from your API
Optional:
- If you need CORS for the API from a browser app, enable skip_auth_preflight (above) and add Access-Control-* headers in NGINX or your API responses.
- If you want to allow multiple audiences (e.g., wizard-oauth too), add to extra_jwt_issuers:
extra_jwt_issuers = [
"<http://keycloak:8080/realms/wizard=wizard-api>",
"<http://keycloak:8080/realms/wizard=wizard-oauth>"
]
If you apply these changes and still see a redirect, please paste:
- The /wizardous/ and /oauth2/auth blocks you’re running
- The oauth2-proxy logs for a failing request (it will say whether it saw/validated a JWT)
- The response headers for GET -I http://nginx/wizardous/health with and without Authorization so we can verify the 401 path is used correctly.Mohamed Zaki
08/31/2025, 12:53 AMMarvin
08/31/2025, 12:53 AMMarvin
08/31/2025, 12:54 AM# existing OIDC settings...
# Ask Keycloak for refresh tokens
scope = "openid profile email offline_access"
# Session lifetime policy at the edge (bounded by Keycloak refresh lifetimes)
cookie_expire = "12h" # overall session cookie lifetime
cookie_refresh = "1h" # proactively refresh tokens every hour
# Recommended to keep cookies small/persistent
session_store_type = "redis"
redis_connection_url = "<redis://redis:6379/2>"
# Keep these from earlier
cookie_secure = false
cookie_samesite = "lax"
set_xauthrequest = true
pass_authorization_header = true
pass_access_token = true
B) Extend API Bearer tokens at /wizardous/*
Because you’re validating tokens at the edge (oauth2-proxy) and keeping the API unaware:
- If you want longer-lived bearer tokens:
- In Keycloak, increase Access Token Lifespan for the API client (wizard-api) or at the realm.
- Clients → wizard-api → Settings → Advanced (override Access Token Lifespan), or use realm defaults (Realm Settings → Tokens).
- Recommended approach: keep access tokens short (e.g., 15–30m) and have clients refresh.
- User-based clients: use Device Flow or Password (dev only) to obtain a refresh_token and exchange it for new access tokens when needed.
- Service-to-service (client_credentials): typically no refresh token; fetch a new access token when the old one expires.
Examples to mint/refresh tokens
- Client credentials (service-to-service):
curl -s -X POST "<http://keycloak:8080/realms/wizard/protocol/openid-connect/token>" \
-d "grant_type=client_credentials" \
-d "client_id=wizard-api" \
-d "client_secret=REDACTED"
- Device flow (per-user, CLI-friendly) — yields refresh_token:
# Start device flow
curl -s -X POST "<http://keycloak:8080/realms/wizard/protocol/openid-connect/device/auth>" \
-d "client_id=wizard-api" \
-d "scope=openid profile email offline_access"
# After user verifies via verification_uri_complete, poll for tokens:
curl -s -X POST "<http://keycloak:8080/realms/wizard/protocol/openid-connect/token>" \
-d "grant_type=urn:ietf:params:oauth:grant-type:device_code" \
-d "client_id=wizard-api" \
-d "device_code=...from previous step..."
- Refresh with a refresh_token (for user-based flows):
curl -s -X POST "<http://keycloak:8080/realms/wizard/protocol/openid-connect/token>" \
-d "grant_type=refresh_token" \
-d "client_id=wizard-api" \
-d "refresh_token=REDACTED"
Security notes
- Prefer short-lived access tokens + refresh over very long-lived access tokens.
- If you do extend lifetimes, consider enabling refresh token rotation and setting sensible SSO/Offline session caps.
- For the UI, oauth2-proxy can silently refresh in the background using the refresh token, as long as the realm/client lifetimes allow it.Marvin
08/31/2025, 12:54 AMMohamed Zaki
08/31/2025, 1:08 AM172.20.0.6 - c21f18a2-4153-41fb-98e9-c9643bc039b5 - - [2025/08/31 01:04:54] nginx GET - "/oauth2/auth" HTTP/1.1 "curl/8.14.1" 401 13 0.086
[2025/08/31 01:04:54] [jwt_session.go:51] Error retrieving session from token in Authorization header: [unable to verify bearer token, could not create session from token: audience from claim aud with value [wizard-api account] does not match with any of allowed audiences map[wizard-oauth:{}], email in id_token (bbbf556f-01c1-4f63-bf4d-43cfcd60a1b6) isn't verified]
Here's oauth2 proxy configs:
provider = "keycloak-oidc"
oidc_issuer_url = "<http://keycloak:8080/realms/wizard>"
client_id = "wizard-oauth"
client_secret = "olcWi1lGcyrU82MtdvNgK8Nm7x4C8ieA"
email_domains = "*"
insecure_oidc_allow_unverified_email = true
http_address = "0.0.0.0:4180"
redirect_url = "<http://nginx:80/oauth2/callback>"
pass_access_token = true
skip_provider_button = true
reverse_proxy = true
cookie_secret = "bHEyY2E0dEZHSTlnY2hJa0FRWnRERURxaDg4Qm5qY3k="
cookie_secure = false # IMPORTANT for HTTP; otherwise the cookie won't be sent
cookie_samesite = "lax"
set_xauthrequest = true
pass_authorization_header = true
# IMPORTANT: accept & validate incoming JWT bearer tokens for the API
skip_jwt_bearer_tokens = true
extra_jwt_issuers = [ "<http://keycloak:8080/realms/wizard=wizard-api>" ]
# Optional (handy for CORS preflight to the API)
skip_auth_preflight = true
Marvin
08/31/2025, 1:08 AMMarvin
08/31/2025, 1:10 AM# Accept & validate incoming JWT bearer tokens for the API
skip_jwt_bearer_tokens = true
extra_jwt_issuers = [ "<http://keycloak:8080/realms/wizard=wizard-api>" ]
# Optional (handy for CORS preflight to the API)
skip_auth_preflight = true
Also ensure these are present (for nginx auth_request + UI):
set_xauthrequest = true
pass_authorization_header = true
pass_access_token = true
Double-check NGINX forwards the Authorization header to the auth subrequest:
location = /oauth2/auth {
proxy_pass <http://oauth2_proxy>;
proxy_set_header Authorization $http_authorization;
proxy_set_header Content-Length "";
proxy_pass_request_body off;
# ...other forwarded headers...
}
Test
- Call oauth2-proxy directly with your token (should return 200):
curl -i -H "Authorization: Bearer $TOKEN" <http://oauth2-proxy:4180/oauth2/auth>
- Through NGINX to your API path:
curl -i -H "Authorization: Bearer $TOKEN" <http://nginx/wizardous/health>
Expected: 200 from your API. If missing/invalid token: 401 JSON from NGINX (no redirect).
Notes
- The “email isn’t verified” part of the log came from oauth2-proxy trying to build a browser session (ID token path). Once extra_jwt_issuers matches and the Bearer JWT is accepted, that check won’t apply to your API calls.
- If you want to accept multiple audiences, list them:
extra_jwt_issuers = [
"<http://keycloak:8080/realms/wizard=wizard-api>",
"<http://keycloak:8080/realms/wizard=wizard-oauth>"
]
If it still 401s, please paste:
- The first few lines of oauth2-proxy startup logs (it will echo parsed config, including extra_jwt_issuers)
- The output of curl -i http://oauth2-proxy:4180/oauth2/auth with and without Authorization so we can see the status codes end-to-end.Mohamed Zaki
08/31/2025, 1:15 AM[2025/08/31 00:49:04] [provider.go:55] Performing OIDC Discovery...
[2025/08/31 00:49:04] [provider.go:55] Performing OIDC Discovery...
[2025/08/31 00:49:04] [providers.go:145] Warning: Your provider supports PKCE methods ["plain" "S256"], but you have not enabled one with --code-challenge-method
[2025/08/31 00:49:04] [oauthproxy.go:156] Skipping JWT tokens from configured OIDC issuer: "<http://keycloak:8080/realms/wizard>"
[2025/08/31 00:49:04] [oauthproxy.go:158] Skipping JWT tokens from extra JWT issuer: "<http://keycloak:8080/realms/wizard=wizard-api>"
[2025/08/31 00:49:04] [oauthproxy.go:166] OAuthProxy configured for Keycloak OIDC Client ID: wizard-oauth
[2025/08/31 00:49:04] [oauthproxy.go:172] Cookie settings: name:_oauth2_proxy secure(https):false httponly:true expiry:168h0m0s domains: path:/ samesite:lax refresh:disabled
Marvin
08/31/2025, 1:15 AMMarvin
08/31/2025, 1:16 AMprovider = "keycloak-oidc"
oidc_issuer_url = "<http://keycloak:8080/realms/wizard>"
client_id = "wizard-oauth"
client_secret = "REDACTED"
redirect_url = "<http://nginx/oauth2/callback>"
reverse_proxy = true
email_domains = "*"
insecure_oidc_allow_unverified_email = true
pass_access_token = true
cookie_secret = "REDACTED"
cookie_secure = false
cookie_samesite = "lax"
set_xauthrequest = true
pass_authorization_header = true
# Accept & validate incoming JWT bearer tokens for the API
skip_jwt_bearer_tokens = true
extra_jwt_issuers = [ "<http://keycloak:8080/realms/wizard=wizard-api>" ]
# Optional: lets CORS preflight skip auth
skip_auth_preflight = true
2) Ensure NGINX forwards Authorization to the auth subrequest
location = /oauth2/auth {
proxy_pass <http://oauth2_proxy>;
proxy_http_version 1.1;
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-Forwarded-Host $host;
proxy_set_header X-Forwarded-Uri $request_uri;
# CRITICAL
proxy_set_header Authorization $http_authorization;
proxy_set_header Content-Length "";
proxy_pass_request_body off;
}
3) Restart oauth2-proxy and confirm the logs
You should see:
- Skipping JWT tokens from configured OIDC issuer: http://keycloak:8080/realms/wizard
- Skipping JWT tokens from extra JWT issuer: http://keycloak:8080/realms/wizard=wizard-api
(no quotes with angle brackets)
4) Test directly against oauth2-proxy, then via NGINX
- Direct:
curl -i -H "Authorization: Bearer $TOKEN" <http://oauth2-proxy:4180/oauth2/auth>
Expect 200 when $TOKEN’s iss is http://keycloak:8080/realms/wizard and aud includes wizard-api.
- Through NGINX to your API:
curl -i -H "Authorization: Bearer $TOKEN" <http://nginx/wizardous/health>
Expect 200 from your API. Without/invalid token should be 401 JSON (no redirect).
If it still 401s after removing the brackets:
- Paste the updated startup logs (first 10 lines)
- Paste the output of the direct check: curl -i -H "Authorization: Bearer $TOKEN" http://oauth2-proxy:4180/oauth2/auth
Docs for reference:
- oauth2-proxy auth_request and JWT settings: https://oauth2-proxy.github.io/oauth2-proxy/docs/configuration/overviewMohamed Zaki
08/31/2025, 1:18 AMprovider = "keycloak-oidc"
oidc_issuer_url = "<http://keycloak:8080/realms/wizard>"
client_id = "wizard-oauth"
client_secret = "olcWi1lGcyrU82MtdvNgK8Nm7x4C8ieA"
email_domains = "*"
insecure_oidc_allow_unverified_email = true
http_address = "0.0.0.0:4180"
redirect_url = "<http://nginx:80/oauth2/callback>"
pass_access_token = true
skip_provider_button = true
reverse_proxy = true
cookie_secret = "bHEyY2E0dEZHSTlnY2hJa0FRWnRERURxaDg4Qm5qY3k="
cookie_secure = false # IMPORTANT for HTTP; otherwise the cookie won't be sent
cookie_samesite = "lax"
set_xauthrequest = true
pass_authorization_header = true
# IMPORTANT: accept & validate incoming JWT bearer tokens for the API
skip_jwt_bearer_tokens = true
extra_jwt_issuers = [ "<http://keycloak:8080/realms/wizard=wizard-api>" ]
# Optional (handy for CORS preflight to the API)
skip_auth_preflight = true
Marvin
08/31/2025, 1:19 AMMarvin
08/31/2025, 1:20 AMdocker compose exec oauth2-proxy sh -lc '
echo "----- /etc/oauth2-proxy.cfg -----";
nl -ba /etc/oauth2-proxy.cfg | sed -n "1,200p";
echo "----- ENV -----";
env | grep -i ^OAUTH2_PROXY || true
'
- Confirm the extra_jwt_issuers
line has NO angle brackets and is exactly:
extra_jwt_issuers = [ "<http://keycloak:8080/realms/wizard=wizard-api>" ]
- If the file looks correct, check there are no env vars overriding it (printed above).
2) Ensure NGINX forwards Authorization to the auth subrequest
Your /oauth2/auth block must include this line:
proxy_set_header Authorization $http_authorization;
Without it, oauth2-proxy never sees the bearer token.
3) Force the settings via env vars (to bypass any config parsing weirdness)
Add to your oauth2-proxy service in compose:
environment:
- OAUTH2_PROXY_SKIP_JWT_BEARER_TOKENS=true
- OAUTH2_PROXY_EXTRA_JWT_ISSUERS=<http://keycloak:8080/realms/wizard=wizard-api>
Then restart oauth2-proxy and confirm in startup logs that the issuer is logged WITHOUT angle brackets:
- Skipping JWT tokens from extra JWT issuer: http://keycloak:8080/realms/wizard=wizard-api
4) Test directly against oauth2-proxy, then through NGINX
- Direct (should be 200 with a valid token):
curl -i -H "Authorization: Bearer $TOKEN" <http://oauth2-proxy:4180/oauth2/auth>
- Through NGINX to your API (should be 200 from API):
curl -i -H "Authorization: Bearer $TOKEN" <http://nginx/wizardous/health>
- No token should be 401 JSON (no redirect).
If it still 401s after the env override:
- Please paste the first 10 lines of the oauth2-proxy startup logs again
- And the response from the direct check:
curl -sv -H "Authorization: Bearer $TOKEN" <http://oauth2-proxy:4180/oauth2/auth> 2>&1 | sed -n '1,/^$/p'
Reference (for exact options and env var names):
- oauth2-proxy: configuration overviewMohamed Zaki
08/31/2025, 1:24 AM[2025/08/31 01:23:27] [provider.go:55] Performing OIDC Discovery...
[2025/08/31 01:23:28] [provider.go:55] Performing OIDC Discovery...
[2025/08/31 01:23:28] [providers.go:145] Warning: Your provider supports PKCE methods ["plain" "S256"], but you have not enabled one with --code-challenge-method
[2025/08/31 01:23:28] [oauthproxy.go:156] Skipping JWT tokens from configured OIDC issuer: "<http://keycloak:8080/realms/wizard>"
[2025/08/31 01:23:28] [oauthproxy.go:158] Skipping JWT tokens from extra JWT issuer: "<http://keycloak:8080/realms/wizard=wizard-api>"
[2025/08/31 01:23:28] [oauthproxy.go:166] OAuthProxy configured for Keycloak OIDC Client ID: wizard-oauth
[2025/08/31 01:23:28] [oauthproxy.go:172] Cookie settings: name:_oauth2_proxy secure(https):false httponly:true expiry:168h0m0s domains: path:/ samesite:lax refresh:disabled
172.20.0.6 - c1a883e3-0564-429f-81d2-a9d0e96fa97d - - [2025/08/31 01:23:44] oauth2-proxy:4180 GET - "/oauth2/auth" HTTP/1.1 "curl/8.14.1" 401 13 0.007
[2025/08/31 01:23:44] [jwt_session.go:51] Error retrieving session from token in Authorization header: [unable to verify bearer token, could not create session from token: audience from claim aud with value [wizard-api account] does not match with any of allowed audiences map[wizard-oauth:{}], email in id_token (bbbf556f-01c1-4f63-bf4d-43cfcd60a1b6) isn't verified]
172.20.0.6 - 0610ed7a-8c30-4c80-9f1e-aca60fe828bf - - [2025/08/31 01:24:16] oauth2-proxy:4180 GET - "/oauth2/auth" HTTP/1.1 "curl/8.14.1" 401 13 0.000
[2025/08/31 01:24:16] [jwt_session.go:51] Error retrieving session from token in Authorization header: [unable to verify bearer token, could not create session from token: audience from claim aud with value [wizard-api account] does not match with any of allowed audiences map[wizard-oauth:{}], email in id_token (bbbf556f-01c1-4f63-bf4d-43cfcd60a1b6) isn't verified]
Marvin
08/31/2025, 1:24 AMMarvin
08/31/2025, 1:26 AMenvironment:
- OAUTH2_PROXY_PROVIDER=keycloak-oidc
- OAUTH2_PROXY_OIDC_ISSUER_URL=<http://keycloak:8080/realms/wizard>
- OAUTH2_PROXY_CLIENT_ID=wizard-oauth
- OAUTH2_PROXY_CLIENT_SECRET=REDACTED
- OAUTH2_PROXY_REDIRECT_URL=<http://nginx/oauth2/callback>
- OAUTH2_PROXY_REVERSE_PROXY=true
# Session/cookie for UI
- OAUTH2_PROXY_COOKIE_SECRET=REDACTED
- OAUTH2_PROXY_COOKIE_SECURE=false
- OAUTH2_PROXY_COOKIE_SAMESITE=lax
- OAUTH2_PROXY_EMAIL_DOMAINS=*
# For nginx auth_request integration
- OAUTH2_PROXY_SET_XAUTHREQUEST=true
- OAUTH2_PROXY_PASS_AUTHORIZATION_HEADER=true
- OAUTH2_PROXY_PASS_ACCESS_TOKEN=true
# CRITICAL: accept and validate Bearer JWTs for API calls
- OAUTH2_PROXY_SKIP_JWT_BEARER_TOKENS=true
- OAUTH2_PROXY_EXTRA_JWT_ISSUERS=<http://keycloak:8080/realms/wizard=wizard-api>
# Optional: avoid blocking OPTIONS preflights
- OAUTH2_PROXY_SKIP_AUTH_PREFLIGHT=true
Tip: You can temporarily remove the config file mount to ensure only env vars are used:
- Remove the volume line that mounts oauth2-proxy.cfg
- Or point it to an empty file
This lets us confirm the env overrides work cleanly.
2) Ensure NGINX forwards the Authorization header to the auth subrequest
Without this, oauth2-proxy won’t see the Bearer token.
location = /oauth2/auth {
proxy_pass <http://oauth2_proxy>;
proxy_http_version 1.1;
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-Forwarded-Host $host;
proxy_set_header X-Forwarded-Uri $request_uri;
proxy_set_header Authorization $http_authorization;
proxy_set_header Content-Length "";
proxy_pass_request_body off;
}
And keep /wizardous/ in “no redirect” mode:
- No error_page 401 = /oauth2/sign_in
- On 401, return JSON (as we outlined earlier)
- Forward Authorization through to your API if you want it to see the token
3) Restart and validate
- Check startup logs now show the raw values (no <>):
Skipping JWT tokens from extra JWT issuer: http://keycloak:8080/realms/wizard=wizard-api
- Test directly against oauth2-proxy:
curl -i -H "Authorization: Bearer $TOKEN" <http://oauth2-proxy:4180/oauth2/auth>
Expect 200 for a token with iss=http://keycloak:8080/realms/wizard and aud including wizard-api.
- Then through NGINX to your API:
curl -i -H "Authorization: Bearer $TOKEN" <http://nginx/wizardous/health>
Expect 200. No/invalid token should yield 401 JSON from NGINX.
If it still fails:
- Run inside the oauth2-proxy container and print effective env and config:
docker compose exec oauth2-proxy sh -lc '
echo "----- ENV (sanitized) -----"; env | sort | grep -E "^OAUTH2_PROXY|^OIDC|^PROVIDER" || true;
echo "----- /etc/oauth2-proxy.cfg (if present) -----"; [ -f /etc/oauth2-proxy.cfg ] && sed -n "1,200p" /etc/oauth2-proxy.cfg || echo "(no file)";
'
- Paste the first 10 lines of startup logs again; they should reflect the issuer without angle brackets.Marvin
08/31/2025, 1:26 AM