missing something with auth_jwt_key_request

Christopher Paul chris.paul at rexconsulting.net
Tue Mar 12 03:09:53 UTC 2024


Hi NGINX-users,

I am running nginx version: nginx/1.25.3 (nginx-plus-r31-p1 on Rocky 9.3 
in a lab, trying to get OIDC authentication working to KeyCloak 23.0.7. 
Attached are the relevant files /etc/nginx.conf and included 
/etc/nginx/conf.d files, most of which are from the nginx-openid-connect 
github repo (https://github.com/nginxinc/nginx-openid-connect).

Keycloak and nginx are running on the same VM.

What am I missing/doing wrong? When I try to hit the server, the 
redirect to Keycloak does not happen. I can tell this for sure by 
running "sudo tcpdump -i lo". There are no packets transmitted to 
localhost:8080. When I "curl -v https://rocky.rexconsulting.net", 
besides no packets between nginx and keycloak, the output of curl is:

* Connected to rocky.rexconsulting.net (10.5.5.90) port 443
* ALPN: curl offers h2,http/1.1
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
*  CAfile: /etc/ssl/cert.pem
*  CApath: none
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.3 (IN), TLS handshake, Unknown (8):
* TLSv1.3 (IN), TLS handshake, Certificate (11):
* TLSv1.3 (IN), TLS handshake, CERT verify (15):
* TLSv1.3 (IN), TLS handshake, Finished (20):
* TLSv1.3 (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384 / [blank] / UNDEF
* ALPN: server accepted http/1.1
* Server certificate:
*  subject: CN=rocky.rexconsulting.net
*  start date: Mar  7 23:46:13 2024 GMT
*  expire date: Jun  5 23:46:12 2024 GMT
*  subjectAltName: host "rocky.rexconsulting.net" matched cert's 
"rocky.rexconsulting.net"
*  issuer: C=US; O=Let's Encrypt; CN=R3
*  SSL certificate verify ok.
*   Certificate level 0: Public key type ? (256/128 Bits/secBits), 
signed using sha256WithRSAEncryption
*   Certificate level 1: Public key type ? (2048/112 Bits/secBits), 
signed using sha256WithRSAEncryption
* using HTTP/1.x
 > GET / HTTP/1.1
 > Host: rocky.rexconsulting.net
 > User-Agent: curl/8.6.0
 > Accept: */*
 >
* old SSL session ID is stale, removing
< HTTP/1.1 401 Unauthorized
< Server: nginx/1.25.3
< Date: Tue, 12 Mar 2024 03:07:32 GMT
< Content-Type: text/html
< Content-Length: 179
< Connection: keep-alive
< WWW-Authenticate: Bearer realm="closed site"
<
<html>
<head><title>401 Authorization Required</title></head>
<body>
<center><h1>401 Authorization Required</h1></center>
<hr><center>nginx/1.25.3</center>
</body>
</html>
* Connection #0 to host rocky.rexconsulting.net left intact

Many thanks for any insight that might be offered on this.

Chris Paul
-------------- next part --------------
user  nginx;
worker_processes  auto;
error_log  /var/log/nginx/error.log debug;
pid        /var/run/nginx.pid;
load_module modules/ngx_http_js_module.so;
load_module modules/ngx_stream_js_module.so;
events {
    worker_connections  1024;
}
http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;
    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';
    access_log  /var/log/nginx/access.log  main;
    sendfile        on;
    keepalive_timeout  65;
    include /etc/nginx/conf.d/*.conf;
}
-------------- next part --------------
# OpenID Connect configuration
#
# Each map block allows multiple values so that multiple IdPs can be supported,
# the $host variable is used as the default input parameter but can be changed.
#
map $host $oidc_authz_endpoint {
    #default "http://127.0.0.1:8080/auth/realms/master/protocol/openid-connect/auth";
    #www.example.com "https://my-idp/oauth2/v1/authorize";
    default "http://127.0.0.1:8080/realms/rexlab/protocol/openid-connect/auth";
}

map $host $oidc_authz_extra_args {
    # Extra arguments to include in the request to the IdP's authorization
    # endpoint.
    # Some IdPs provide extended capabilities controlled by extra arguments,
    # for example Keycloak can select an IdP to delegate to via the
    # "kc_idp_hint" argument.
    # Arguments must be expressed as query string parameters and URL-encoded
    # if required.
    default "";
    #www.example.com "kc_idp_hint=another_provider"
}

map $host $oidc_token_endpoint {
    #default "http://127.0.0.1:8080/auth/realms/master/protocol/openid-connect/token";
    default "http://127.0.0.1:8080/auth/realms/rexlab/protocol/openid-connect/token";
}

map $host $oidc_jwt_keyfile {
    #default "http://127.0.0.1:8080/auth/realms/master/protocol/openid-connect/certs";
    default "http://127.0.0.1:8080/realms/rexlab/protocol/openid-connect/certs";
}

map $host $oidc_client {
    default "nginx-plus";
}

map $host $oidc_pkce_enable {
    default 0;
}

map $host $oidc_client_secret {
    default "UxPA37ZTMv36mTGSZhfSTFCl91YYzwcx";
}

map $host $oidc_scopes {
    default "openid+profile+email+offline_access";
}

map $host $oidc_logout_redirect {
    # Where to send browser after requesting /logout location. This can be
    # replaced with a custom logout page, or complete URL.
    default "/_logout"; # Built-in, simple logout page
}

map $host $oidc_hmac_key {
    # This should be unique for every NGINX instance/cluster
    default "f3etJkRhybOLWPAt59lWN4GmXz";
}

map $host $zone_sync_leeway {
    # Specifies the maximum timeout for synchronizing ID tokens between cluster
    # nodes when you use shared memory zone content sync. This option is only
    # recommended for scenarios where cluster nodes can randomly process
    # requests from user agents and there may be a situation where node "A"
    # successfully received a token, and node "B" receives the next request in
    # less than zone_sync_interval.
    default 0; # Time in milliseconds, e.g. (zone_sync_interval * 2 * 1000)
}

map $proto $oidc_cookie_flags {
    http  "Path=/; SameSite=lax;"; # For HTTP/plaintext testing
    https "Path=/; SameSite=lax; HttpOnly; Secure;"; # Production recommendation
}

map $http_x_forwarded_port $redirect_base {
    ""      $proto://$host:$server_port;
    default $proto://$host:$http_x_forwarded_port;
}

map $http_x_forwarded_proto $proto {
    ""      $scheme;
    default $http_x_forwarded_proto;
}

# ADVANCED CONFIGURATION BELOW THIS LINE
# Additional advanced configuration (server context) in openid_connect.server_conf

# JWK Set will be fetched from $oidc_jwks_uri and cached here - ensure writable by nginx user
proxy_cache_path /var/cache/nginx/jwk levels=1 keys_zone=jwk:64k max_size=1m;

# Change timeout values to at least the validity period of each token type
keyval_zone zone=oidc_id_tokens:1M     state=conf.d/oidc_id_tokens.json     timeout=1h;
keyval_zone zone=oidc_access_tokens:1M state=conf.d/oidc_access_tokens.json timeout=1h;
keyval_zone zone=refresh_tokens:1M     state=conf.d/refresh_tokens.json     timeout=8h;
keyval_zone zone=oidc_pkce:128K timeout=90s; # Temporary storage for PKCE code verifier.

keyval $cookie_auth_token $session_jwt   zone=oidc_id_tokens;     # Exchange cookie for JWT
keyval $cookie_auth_token $access_token  zone=oidc_access_tokens; # Exchange cookie for access token
keyval $cookie_auth_token $refresh_token zone=refresh_tokens;     # Exchange cookie for refresh token
keyval $request_id $new_session          zone=oidc_id_tokens;     # For initial session creation
keyval $request_id $new_access_token     zone=oidc_access_tokens;
keyval $request_id $new_refresh          zone=refresh_tokens; # ''
keyval $pkce_id $pkce_code_verifier      zone=oidc_pkce;

auth_jwt_claim_set $jwt_audience aud; # In case aud is an array
js_import oidc from conf.d/openid_connect.js;

# vim: syntax=nginx
-------------- next part --------------
# This is the backend application we are protecting with OpenID Connect
upstream my_backend {
    zone my_backend 64k;
    server 10.0.0.1:80;
}

# Custom log format to include the 'sub' claim in the REMOTE_USER field
log_format main_jwt '$remote_addr - $jwt_claim_sub [$time_local] "$request" $status '
                    '$body_bytes_sent "$http_referer" "$http_user_agent" "$http_x_forwarded_for"';

# The frontend server - reverse proxy with OpenID Connect authentication
#
server {
    include conf.d/openid_connect.server_conf; # Authorization code flow and Relying Party processing
    error_log /var/log/nginx/error.log debug;  # Reduce severity level as required

    listen 8010; # Use SSL/TLS in production
    
    location / {
        # This site is protected with OpenID Connect
        auth_jwt "" token=$session_jwt;
        error_page 401 = @do_oidc_flow;

        #auth_jwt_key_file $oidc_jwt_keyfile; # Enable when using filename
        auth_jwt_key_request /_jwks_uri; # Enable when using URL

        # Successfully authenticated users are proxied to the backend,
        # with 'sub' claim passed as HTTP header
        proxy_set_header username $jwt_claim_sub;

        # Bearer token is uses to authorize NGINX to access protected backend
        #proxy_set_header Authorization "Bearer $access_token";
        
        # Intercept and redirect "401 Unauthorized" proxied responses to nginx
        # for processing with the error_page directive. Necessary if Access Token
        # can expire before ID Token.
        #proxy_intercept_errors on;

        proxy_pass http://my_backend; # The backend site/app

        access_log /var/log/nginx/access.log main_jwt;
    }
}

# vim: syntax=nginx
-------------- next part --------------
server {
    listen 80;
    server_name rocky.rexconsulting.net;
    return 301 https://$host$request_uri;
}

server {
#    listen       80 default_server;
    listen       443 ssl;
#    auth_jwt   "API";
#    auth_jwt_key_request "http://localhost:8080/realms/rexlab/protocol/openid-connect/certs";
#    auth_jwt_type     encrypted;
    access_log /var/log/nginx/access.log;
    error_log /var/log/nginx/error.log debug;

    server_name  rocky.rexconsulting.net;
    ssl_certificate     /etc/ssl/nginx/nginx.crt;
    ssl_certificate_key /etc/ssl/nginx/nginx.key;
    #    ssl_verify_client       on;
    #    ssl_trusted_certificate /etc/ssl/cachain.pem;
    #    ssl_ocsp                on; # Enable OCSP validation

    #access_log  /var/log/nginx/host.access.log  main;

    location / {
        auth_jwt_key_request /_jwks_uri;
        auth_jwt "closed site" token=$cookie_auth_token;
        root   /usr/share/nginx/html;
        index  index.html index.htm;
    }

    location = /_jwks_uri {
        internal;
        #proxy_pass $oidc_jwt_keyfile; # Uses the mapped value
        proxy_pass "http://127.0.0.1:8080/realms/rexlab/protocol/openid-connect/certs";
        proxy_cache_valid 200 1d; # Caches the keys for a day, adjust as needed
        proxy_cache jwk; # Assumes you have a proxy_cache_path defined with `jwk` as the key
        error_page 401 = @error401;
}

    location @error401 {
        internal;
        proxy_pass $oidc_authz_endpoint; # Redirect to Keycloak for login
    }
    #error_page  404              /404.html;
    # redirect server error pages to the static page /50x.html
    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }
}


More information about the nginx mailing list