Git Product home page Git Product logo

nginx-openid-connect's Introduction

nginx-openid-connect

Reference implementation of NGINX Plus as relying party for OpenID Connect authentication

Description

This repository describes how to enable OpenID Connect integration for NGINX Plus. The solution depends on NGINX Plus components (auth_jwt module and key-value store) and as such is not suitable for open source NGINX.

OpenID Connect components

Figure 1. High level components of an OpenID Connect environment

This implementation assumes the following environment:

  • The identity provider (IdP) supports OpenID Connect 1.0
  • The authorization code flow is in use
  • NGINX Plus is configured as a relying party
  • The IdP knows NGINX Plus as a confidential client or a public client using PKCE

With this environment, both the client and NGINX Plus communicate directly with the IdP at different stages during the initial authentication event.

OpenID Connect protocol diagram Figure 2. OpenID Connect authorization code flow protocol

NGINX Plus is configured to perform OpenID Connect authentication. Upon a first visit to a protected resource, NGINX Plus initiates the OpenID Connect authorization code flow and redirects the client to the OpenID Connect provider (IdP). When the client returns to NGINX Plus with an authorization code, NGINX Plus exchanges that code for a set of tokens by communicating directly with the IdP.

The ID Token received from the IdP is validated. NGINX Plus then stores the ID token in the key-value store, issues a session cookie to the client using a random string, (which becomes the key to obtain the ID token from the key-value store) and redirects the client to the original URI requested prior to authentication.

Subsequent requests to protected resources are authenticated by exchanging the session cookie for the ID Token in the key-value store. JWT validation is performed on each request, as normal, so that the ID Token validity period is enforced.

For more information on OpenID Connect and JWT validation with NGINX Plus, see Authenticating Users to Existing Applications with OpenID Connect and NGINX Plus.

Access Tokens

Access tokens are used in token-based authentication to allow OIDC client to access a protected resource on behalf of the user. NGINX Plus receives an access token after a user successfully authenticates and authorizes access, and then stores it in the key-value store. NGINX Plus can pass that token on the HTTP Authorization header as a Bearer token for every request that is sent to the downstream application.

Note: NGINX Plus does not verify the validity of the access token on each request, as we do with the ID token, so we cannot know if the access token has already expired or not. So, if access token lifetime is less than the ID token lifetime, you have to use the proxy_intercept_errors on directive, which will intercept and redirect 401 Unauthorized responses to NGINX in order to refresh the access token.

Refresh Tokens

If a refresh token was received from the IdP then it is also stored in the key-value store. When validation of the ID Token fails (typically upon expiry) then NGINX Plus sends the refresh token to the IdP. If the user's session is still valid at the IdP then a new ID token is received, validated, and updated in the key-value store. The refresh process is seamless to the client.

Logout

Requests made to the /logout location invalidate both the ID token, access token and refresh token by erasing them from the key-value store. Therefore, subsequent requests to protected resources will be treated as a first-time request and send the client to the IdP for authentication. Note that the IdP may issue cookies such that an authenticated session still exists at the IdP.

Multiple IdPs

Where NGINX Plus is configured to proxy requests for multiple websites or applications, or user groups, these may require authentication by different IdPs. Separate IdPs can be configured, with each one matching on an attribute of the HTTP request, e.g. hostname or part of the URI path.

Note: When validating OpenID Connect tokens, NGINX Plus can be configured to read the signing key (JWKS) from disk, or a URL. When using multiple IdPs, each one must be configured to use the same method. It is not possible to use a mix of both disk and URLs for the map…$oidc_jwt_keyfile variable.

Installation

Start by installing NGINX Plus. In addition, the NGINX JavaScript module (njs) is required for handling the interaction between NGINX Plus and the OpenID Connect provider (IdP). Install the njs module after installing NGINX Plus by running one of the following:

$ sudo apt install nginx-plus-module-njs for Debian/Ubuntu

$ sudo yum install nginx-plus-module-njs for CentOS/RHEL

The njs module needs to be loaded by adding the following configuration directive near the top of nginx.conf.

load_module modules/ngx_http_js_module.so;

Finally, create a clone of the GitHub repository.

$ git clone https://github.com/nginxinc/nginx-openid-connect

Note: There is a branch for each NGINX Plus release. Switch to the correct branch to ensure compatibility with the features and syntax of each release. The main branch works with the most recent NGINX Plus and JavaScript module releases.

All files can be copied to /etc/nginx/conf.d

Non-standard directories

The GitHub repository contains include files for NGINX configuration, and JavaScript code for token exchange and initial token validation. These files are referenced with a relative path (relative to /etc/nginx). If NGINX Plus is running from a non-standard location then copy the files from the GitHub repository to /path/to/conf/conf.d and use the -p flag to start NGINX with a prefix path that specifies the location where the configuration files are located.

$ nginx -p /path/to/conf -c /path/to/conf/nginx.conf

Running in containers

This implementation is suitable for running in a container provided that the base image includes the NGINX JavaScript module. The GitHub repository is designed to facilitate testing with a container by binding the cloned repository to a mount volume on the container.

$ cd nginx-openid-connect
$ docker run -d -p 8010:8010 -v $PWD:/etc/nginx/conf.d nginx-plus nginx -g 'daemon off; load_module modules/ngx_http_js_module.so;'

Running behind another proxy or load balancer

When NGINX Plus is deployed behind another proxy, the original protocol and port number are not available. NGINX Plus needs this information to construct the URIs it passes to the IdP and for redirects. By default NGINX Plus looks for the X-Forwarded-Proto and X-Forwarded-Port request headers to construct these URIs.

Configuring your IdP

  • Create an OpenID Connect client to represent your NGINX Plus instance

    • Choose the authorization code flow
    • Set the redirect URI to the address of your NGINX Plus instance (including the port number), with /_codexch as the path, e.g. https://my-nginx.example.com:443/_codexch
    • Ensure NGINX Plus is configured as a confidential client (with a client secret) or a public client (with PKCE S256 enabled)
    • Make a note of the client ID and client secret if set
  • If your IdP supports OpenID Connect Discovery (usually at the URI /.well-known/openid-configuration) then use the configure.sh script to complete configuration. In this case you can skip the next section. Otherwise:

    • Obtain the URL for jwks_uri or download the JWK file to your NGINX Plus instance
    • Obtain the URL for the authorization endpoint
    • Obtain the URL for the token endpoint

Configuring NGINX Plus

Configuration can typically be completed automatically by using the configure.sh script.

Manual configuration involves reviewing the following files so that they match your IdP(s) configuration.

  • openid_connect_configuration.conf - this contains the primary configuration for one or more IdPs in map{} blocks

    • Modify all of the map…$oidc_ blocks to match your IdP configuration
    • Modify the URI defined in map…$oidc_logout_redirect to specify an unprotected resource to be displayed after requesting the /logout location
    • Set a unique value for $oidc_hmac_key to ensure nonce values are unpredictable
    • If NGINX Plus is deployed behind another proxy or load balancer, modify the map…$redirect_base and map…$proto blocks to define how to obtain the original protocol and port number.
  • frontend.conf - this is the reverse proxy configuration

    • Modify the upstream group to match your backend site or app
    • Configure the preferred listen port and enable SSL/TLS configuration
    • Modify the severity level of the error_log directive to suit the deployment environment
    • Comment/uncomment the auth_jwt_key_file or auth_jwt_key_request directives based on whether $oidc_jwt_keyfile is a file or URI, respectively
    • Uncomment the proxy_set_header Authorization "Bearer $access_token" directive if you want to pass access/bearer token in HTTP header to the protected backend/upstream
    • Uncoment the proxy_intercept_errors on directive if the access token lifetime is less than the ID token lifetime
  • openid_connect.server_conf - this is the NGINX configuration for handling the various stages of OpenID Connect authorization code flow

    • No changes are usually required here
    • Modify the resolver directive to match a DNS server that is capable of resolving the IdP defined in $oidc_token_endpoint
    • If using auth_jwt_key_request to automatically fetch the JWK file from the IdP then modify the validity period and other caching options to suit your IdP
  • openid_connect.js - this is the JavaScript code for performing the authorization code exchange and nonce hashing

    • No changes are required unless modifying the code exchange or validation process

Configuring the Key-Value Store

The key-value store is used to maintain persistent storage for ID tokens and refresh tokens. The default configuration should be reviewed so that it suits the environment. This is part of the advanced configuration in openid_connect_configuration.conf.

keyval_zone zone=oidc_id_tokens:1M     state=/var/lib/nginx/state/oidc_id_tokens.json     timeout=1h;
keyval_zone zone=oidc_access_tokens:1M state=/var/lib/nginx/state/oidc_access_tokens.json timeout=1h;
keyval_zone zone=refresh_tokens:1M     state=/var/lib/nginx/state/refresh_tokens.json     timeout=8h;
keyval_zone zone=oidc_pkce:128K timeout=90s;

Each of the keyval_zone parameters are described below.

  • zone - Specifies the name of the key-value store and how much memory to allocate for it. Each session will typically occupy 1-2KB, depending on the size of the tokens, so scale this value to exceed the number of unique users that may authenticate.

  • state (optional) - Specifies where all of the ID Tokens in the key-value store are saved, so that sessions will persist across restart or reboot of the NGINX host. The NGINX Plus user account, typically nginx, must have write permission to the directory where the state file is stored. Consider creating a dedicated directory for this purpose.

  • timeout - Expired tokens are removed from the key-value store after the timeout value. This should be set to value slightly longer than the JWT validity period. JWT validation occurs on each request, and will fail when the expiry date (exp claim) has elapsed. If JWTs are issued without an exp claim then set timeout to the desired session duration. If JWTs are issued with a range of validity periods then set timeout to exceed the longest period.

  • sync (optional) - If deployed in a cluster, the key-value store may be synchronized across all instances in the cluster, so that all instances are able to create and validate authenticated sessions. Each instance must be configured to participate in state sharing with the zone_sync module and by adding the sync parameter to the keyval_zone directives above.

Session Management

The NGINX Plus API is enabled in openid_connect.server_conf so that sessions can be monitored. The API can also be used to manage the current set of active sessions.

To query the current sessions in the key-value store:

$ curl localhost:8010/api/6/http/keyvals/oidc_id_tokens

To delete a single session:

$ curl -iX PATCH -d '{"<session ID>":null}' localhost:8010/api/6/http/keyvals/oidc_id_tokens
$ curl -iX PATCH -d '{"<session ID>":null}' localhost:8010/api/6/http/keyvals/oidc_access_tokens
$ curl -iX PATCH -d '{"<session ID>":null}' localhost:8010/api/6/http/keyvals/refresh_tokens

To delete all sessions:

$ curl -iX DELETE localhost:8010/api/6/http/keyvals/oidc_id_tokens
$ curl -iX DELETE localhost:8010/api/6/http/keyvals/oidc_access_tokens
$ curl -iX DELETE localhost:8010/api/6/http/keyvals/refresh_tokens

Real time monitoring

The openid_connect.server_conf file defines several status_zone directives to collect metrics about OpenID Connect activity and errors. Separate metrics counters are recorded for:

  • OIDC start - New sessions are counted here. See step 2 in Figure 2, above. Success is recorded as a 3xx response.

  • OIDC code exchange - Counters are incremented here when the browser returns to NGINX Plus after authentication. See steps 6-10 in Figure 2, above. Success is recorded as a 3xx response.

  • OIDC logout - Requests to the /logout URI are counted here. Success is recorded as a 3xx response.

  • OIDC error - Counters are incremented here when errors in the code exchange process are actively detected. Typically there will be a corresponding error_log entry.

To obtain the current set of metrics:

$ curl localhost:8010/api/6/http/location_zones

In addition, the NGINX Plus Dashboard can be configured to visualize the monitoring metrics in a GUI.

Troubleshooting

Any errors generated by the OpenID Connect flow are logged to the error log, /var/log/nginx/error.log. Check the contents of this file as it may include error responses received by the IdP. The level of detail recorded can be modified by adjusting the severity level of the error_log directive.

  • 400 error from IdP

    • This is typically caused by incorrect configuration related to the client ID and client secret.
    • Check the values of the map…$oidc_client and map…$oidc_client_secret variables against the IdP configuration.
  • 500 error from nginx after successful authentication

    • Check for could not be resolved and empty JWK set while sending to client messages in the error log. This is common when NGINX Plus cannot reach the IdP's jwks_uri endpoint.
    • Check the map…$oidc_jwt_keyfile variable is correct.
    • Check the resolver directive in openid_connect.server_conf is reachable from the NGINX Plus host.
    • Check for OIDC authorization code sent but token response is not JSON. messages in the error log. This is common when NGINX Plus cannot decompress the IdP's response. Add the following configuration snippet to the /_jwks_uri and /_token locations to openid_connect.server_conf:
    proxy_set_header Accept-Encoding "gzip";
  • Authentication is successful but browser shows too many redirects

    • This is typically because the JWT sent to the browser cannot be validated, resulting in 'authorization required' 401 response and starting the authentication process again. But the user is already authenticated so is redirected back to NGINX, hence the redirect loop.
    • Avoid using auth_jwt_require directives in your configuration because this can also return a 401 which is indistinguishable from missing/expired JWT.
    • Check the error log /var/log/nginx/error.log for JWT/JWK errors.
    • Ensure that the JWK file (map…$oidc_jwt_keyfile variable) is correct and that the nginx user has permission to read it.
  • Logged out but next request does not require authentication

    • This is typically caused by the IdP issuing its own session cookie(s) to the client. NGINX Plus sends the request to the IdP for authentication and the IdP immediately sends back a new authorization code because the session is still valid.
    • Check your IdP configuration if this behavior is not desired.
  • Failed SSL/TLS handshake to IdP

    • Indicated by error log messages including peer closed connection in SSL handshake (104: Connection reset by peer) while SSL handshaking to upstream.
    • This can occur when the IdP requires Server Name Indication (SNI) information as part of the TLS handshake. Additional configuration is required to satisfy this requirement.
    • Edit openid_connect.server_conf and for each of the /_jwks_uri, /_token, and /_refresh locations, add the following configuration snippet:
proxy_set_header Host <IdP hostname>;
proxy_ssl_name        <IdP hostname>;
  • Invalid access token Users may receive a 401 response with an optional "Invalid token" message despite successful authentication. There are several reasons why an OIDC access token might not be accepted by the upstream server, even if it has not expired:
    • Incorrect backend server configuration. NGINX Plus sends the bearer token in the HTTP Authorization header, but the backend application expects it in a specific cookie.
    • The token has been tampered with. OIDC access tokens are digitally signed by the authorization server to ensure their authenticity. If the token has been modified in any way, the signature will no longer be valid, and the token will be considered invalid.

    Note: The scope of an OIDC access token is independent of its validity. Even if an OIDC access token is not expired and has not been revoked, it may still be considered invalid if it does not have the necessary scope for the requested action. Please check the $oidc_scopes variable in the openid_connect_configuration.conf file.

Support

This reference implementation for OpenID Connect is supported for NGINX Plus subscribers.

Changelog

  • R15 Initial release of OpenID Connect reference implementation
  • R16 Added support for opaque session tokens using key-value store
  • R17 Configuration now supports JSON Web Key (JWK) set to be obtained by URI
  • R18 Opaque session tokens now used by default. Added support for refresh tokens. Added /logout location.
  • R19 Minor bug fixes
  • R22 Separate configuration file, supports multiple IdPs. Configurable scopes and cookie flags. JavaScript is imported as an indepedent module with js_import. Container-friendly logging. Additional metrics for OIDC activity.
  • R23 PKCE support. Added support for deployments behind another proxy or load balancer.
  • R28 Access token support. Added support for access token to authorize NGINX to access protected backend.

nginx-openid-connect's People

Contributors

alanwilkie-finocomp avatar craig-seeman avatar fabriziofiorucci avatar lcrilly avatar route443 avatar shawnhankim avatar tippexs avatar writemike avatar yiksanchan avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

nginx-openid-connect's Issues

JWT and Claims Sent to the web service?

Are the JWT and claims forwarded to the backend web service? How does the web service get any information about who was logged in, his identity, permissions etc?

I can't use the access_token from our identity provider as a variable in nginx?

The response from our idp when requesting a token looks like this:
{"access_token":"...",
"refresh_token":"...",
"scope":"...",
"id_token":"...",
"token_type":"...",
"expires_in":...,
"nonce":"..."
}

Next to the id_token which is available in nginx as $session_jwt, I would like to use the access_token to send it as a header to the proxied application.

Issues with OpenidConnect.js and openid_connect.server_conf

I am trying out this module by following tutorial and ran into several issues which indicates either this tutorial is out of date or there are issues with this module.
https://www.nginx.com/blog/nginx-plus-ingress-controller-for-kubernetes-openid-connect-azure-ad/

I gave up after error 3 to confirm if I am doing something very wrong. Followed tutorial as is (with some changes as it looks there are changes in kubectl config files). We are currently evaluating this module and running in several issues getting this off the ground :(

Error 1:

2020/06/24 02:38:11 [emerg] 52#52: "resolver" directive is duplicate in /etc/nginx/conf.d/openid_connect.server_conf:3
E0624 02:38:11.981791       1 controller.go:459] Error updating endpoints for [default/cafe-ingress]: Error reloading NGINX when updating endpoints: nginx reload failed: Command /usr/sbin/nginx -s reload stdout: ""
stderr: "nginx: [emerg] \"resolver\" directive is duplicate in /etc/nginx/conf.d/openid_connect.server_conf:3\n"
finished with error: exit status 1

Commented to resolve

resolver 8.8.8.8; # For DNS lookup of IdP endpoints;

Error 2:

I0624 03:26:02.375531       1 event.go:278] Event(v1.ObjectReference{Kind:"Secret", Namespace:"nginx-ingress", Name:"default-server-secret", UID:"e5575df7-1735-458e-86f4-e4f5abe85bad", APIVersion:"v1", ResourceVersion:"17870", FieldPath:""}): type: 'Warning' reason: 'UpdatedWithError' the special Secret nginx-ingress/default-server-secret was updated, but not applied: Error when reloading NGINX when updating the special Secrets: nginx reload failed: Command /usr/sbin/nginx -s reload stdout: ""
stderr: "nginx: [emerg] \"subrequest_output_buffer_size\" directive is duplicate in /etc/nginx/conf.d/openid_connect.server_conf:4\n"
finished with error: exit status 1

Commented to resolve

subrequest_output_buffer_size 32k; # To fit a complete tokenset response

Error 3:

I0624 03:27:02.109062       1 event.go:278] Event(v1.ObjectReference{Kind:"ConfigMap", Namespace:"nginx-ingress", Name:"nginx-config", UID:"9590b8b7-046c-4d1a-906c-81c3de426fd9", APIVersion:"v1", ResourceVersion:"17956", FieldPath:""}): type: 'Warning' reason: 'UpdatedWithError' Configuration from nginx-ingress/nginx-config was updated but was not applied: Error when updating config from ConfigMap: nginx reload failed: Command /usr/sbin/nginx -s reload stdout: ""
stderr: "nginx: [emerg] SyntaxError: Illegal export statement in openid_connect.js:8, included in /etc/nginx/conf.d/default-cafe-ingress.conf:21\n"
finished with error: exit status 1

looks now issue at

export default {auth, codeExchange, validateIdToken, logout};

Docs: access token and new endpoints (/login, /userinfo, /v2/logout)

Background:

  • Access token is added in the NGINX Plus OIDC.
  • New endpoints are added in the issue of #55
    • /login endpoint
    • /userinfo endpoint
    • `/v2/logout endpoint
  • Quick start guide is linked into this link to minimize the change of files. It is subject to change by creating a branch (e.g. demo) in the repo of https://github.com/nginxinc/nginx-openid-connect.
  • The docs and comments of configuration for the above new features need to be enhanced for customer convenience.

AC:

How to change redirect_base variable on Nginx side without affecting authorization flow?

Hello,

As it is: we have many backend applications on which we want to enforce authentication. And Nginx should pass the correct request URI, which will be in the client’s configuration on Identity Provider’s side (KeyCloak). So, for each application we must create additional record in KeyCloak’s client parameters. In a situation where we have thousands of applications this is impossible, so we must change redirect_base variable on Nginx side. But when we tried to do this – request didn’t get some cookies (auth_redir primarily) and authorization flow didn’t work correctly.

Could you please suggest how to change this variable correctly without affecting the authorization flow? Or maybe there are other ways to fix the root cause of the problem?

Add OIDC end session endpoint and custom query params

As a Product Manager,

I want to integrate with IdP's end session endpoint to terminate the user session on the IdP's side as the current NJS implementation clear the token itself, and the IdP's authenticated session still exists at the IdP. In addition to that I want to support the logout using the customizable variable regardless of different query parameters from each IdP.

AC:

  • Add a map variable of $oidc_end_session_endpoint as same as authorization and token endpoints in the openid_connect_configuration.conf.

  • Add a map variable of $oidc_logout_landing_page to determine where to redirect browser after successful logout from the IdP.

  • Add a map variable of $oidc_end_session_query_params to support different query parameters per each IdP.

  • Enhanc /logout location:

    • Add query parameters using $oidc_end_session_query_params for the $oidc_end_session_endpoint.
    • NGINX Plus: cleared tokens.
    • Redirected to the $oidc_end_session_endpoint to start ending session in the IdP.
  • Enhanc /_logout location:

    • Redirected by IdP when IdP successfully finished the session.
    • Clean cookies
    • NGINX Plus: Redirect to the $oidc_logout_landing_page.

Add OIDC landing page for NGINX to redirect after successful OIDC login

AC:

  • Add a map variable of $oidc_landing_page to determine where to send browser after successful OIDC login from the IdP.

Out of Scope:

  • Add a /login location to start OIDC flow as this feature will be part of application level instead of a reference implementation for a simple proxy in this repo regarding the following requirement.
    As a Product Manager,
    
    I want a SPA to start a landing page of Single Page App(SPA) without OpenID Connect (OIDC) flow. In the landing page, I want a SPA to just show default data by calling public APIs without API authorization. 
    
    Afterwards, I want to start the OIDC flow when I just click `login` button to display information in detail by calling private APIs based on API authorization using an access token.
    

Loop 302 after expire access token

Good day!
I ran my test installation through this guide (https://docs.nginx.com/nginx/deployment-guides/single-sign-on/keycloak/)

And started to get infinite loop after access token expired.
It seems strange.

First step go the site https://main.example.com/
step 2 -> 302 redirect to https://keycloak.example.com/
step 3 -> auth in keycloak
step 4 -> 302 to https://main.example.com/
step 5 -> after 5 minutes (access token ttl) browser started return 302 from main.example.com to keycloak, keycloak send 302 to main.example and infinite loop....

server {
  listen 443 ssl;
  server_name main.example.com;
  include conf.d/locations-openid;

  location / {
    include conf.d/locations-openid_auth_jwt;
    proxy_pass https://upstream;
  }

}



# conf.d/locations-openid_auth_jwt;
auth_jwt "" token=$session_jwt;
error_page 401 = @do_oidc_flow;
auth_jwt_key_request /_jwks_uri; # Enable when using URL
proxy_set_header username $jwt_claim_sub;


#conf.d/locations-openid
# Advanced configuration START
set $internal_error_message "NGINX / OpenID Connect login failure\n";
set $pkce_id "";
subrequest_output_buffer_size 32k; # To fit a complete tokenset response
gunzip on; # Decompress IdP responses if necessary
# Advanced configuration END
location = /_jwks_uri {
    auth_jwt off;
    internal;
    proxy_cache jwk;                              # Cache the JWK Set recieved from IdP
    proxy_cache_valid 200 12h;                    # How long to consider keys "fresh"
    proxy_cache_use_stale error timeout updating; # Use old JWK Set if cannot reach IdP
    proxy_ssl_server_name on;                     # For SNI to the IdP
    proxy_method GET;                             # In case client request was non-GET
    proxy_set_header Content-Length "";           # ''
    proxy_set_header Accept-Encoding "gzip";      # fixed OIDC authorization code sent but token response is not JSON
    proxy_pass $oidc_jwt_keyfile;                 # Expecting to find a URI here
    proxy_ignore_headers Cache-Control Expires Set-Cookie; # Does not influence caching
}
location @do_oidc_flow {
    auth_jwt off;
    status_zone "OIDC start";
    js_content oidc.auth;
    default_type text/plain; # In case we throw an error
}
set $redir_location "/_codexch";
location = /_codexch {
    # This location is called by the IdP after successful authentication
    auth_jwt off;
    status_zone "OIDC code exchange";
    js_content oidc.codeExchange;
    error_page 500 502 504 @oidc_error;
}
location = /_token {
    # This location is called by oidcCodeExchange(). We use the proxy_ directives
    # to construct the OpenID Connect token request, as per:
    #  http://openid.net/specs/openid-connect-core-1_0.html#TokenRequest
    auth_jwt off;
    internal;
    proxy_ssl_server_name on; # For SNI to the IdP
    proxy_set_header      Content-Type "application/x-www-form-urlencoded";
    proxy_set_header Accept-Encoding "gzip"; # fixed OIDC authorization code sent but token response is not JSON
    proxy_set_body        "grant_type=authorization_code&client_id=$oidc_client&$args&redirect_uri=$redirect_base$redir_location";
    proxy_method          POST;
    proxy_pass            $oidc_token_endpoint;
}
location = /_refresh {
    # This location is called by oidcAuth() when performing a token refresh. We
    # use the proxy_ directives to construct the OpenID Connect token request, as per:
    #  https://openid.net/specs/openid-connect-core-1_0.html#RefreshingAccessToken
    auth_jwt off;
    internal;
    proxy_ssl_server_name on; # For SNI to the IdP
    proxy_set_header      Content-Type "application/x-www-form-urlencoded";
    proxy_set_body        "grant_type=refresh_token&refresh_token=$arg_token&client_id=$oidc_client&client_secret=$oidc_client_secret";
    proxy_method          POST;
    proxy_pass            $oidc_token_endpoint;
}
location = /_id_token_validation {
    # This location is called by oidcCodeExchange() and oidcRefreshRequest(). We use
    # the auth_jwt_module to validate the OpenID Connect token response, as per:
    #  https://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation
    internal;
    auth_jwt "" token=$arg_token;
    js_content oidc.validateIdToken;
    error_page 500 502 504 @oidc_error;
}
location = /logout {
    auth_jwt off;
    status_zone "OIDC logout";
    add_header Set-Cookie "auth_token=; $oidc_cookie_flags"; # Send empty cookie
    add_header Set-Cookie "auth_redir=; $oidc_cookie_flags"; # Erase original cookie
    js_content oidc.logout;
}
location = /_logout {
    # This location is the default value of $oidc_logout_redirect (in case it wasn't configured)
    auth_jwt off;
    default_type text/plain;
    return 200 "Logged out\n";
}
location @oidc_error {
    # This location is called when oidcAuth() or oidcCodeExchange() returns an error
    auth_jwt off;
    status_zone "OIDC error";
    default_type text/plain;
    return 500 $internal_error_message;
}

# openid.conf

# 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 "https://keycloak/realms/main/protocol/openid-connect/auth";
    #www.example.com "https://my-idp/oauth2/v1/authorize";
}

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 "https://keycloak/realms/main/protocol/openid-connect/token";
}

map $host $oidc_jwt_keyfile {
    default "https://keycloak/realms/main/protocol/openid-connect/certs";
}

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

map $host $oidc_pkce_enable {
    default 0;
}

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

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 "key";
}

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; Max-Age=86400; 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_port $redirect_base {
    default $proto://$host;
}

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:100m max_size=512m;

# Change timeout values to at least the validity period of each token type
keyval_zone zone=oidc_id_tokens:100M timeout=4h;
keyval_zone zone=oidc_access_tokens:100M timeout=4h;
keyval_zone zone=refresh_tokens:10M timeout=1d;
keyval_zone zone=oidc_pkce:512K; # 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 js/openid_connect.js;


and unmodified js/openid_connect.js; from main branch

update documentation - Azure AD IdP

Hello,

To support Azure AD as an IdP, it's necessary to set header Origin. If not, this issue here is encountered.
Error looks like: [error] 10#10: *1 js: OIDC error from IdP when sending authorization code: invalid_request, AADSTS9002327: Tokens issued for the 'Single-Page Application' client-type may only be redeemed via cross-origin requests.

Fix is to add proxy_set_header Origin $host; in locations /_token and /_refresh

Could you update the documentation?

Best Regards

Capture access token from IdP

Background:

  • Current NJS implementation disregard the access_token that is being sent by the IdP and only uses the id_token to get stored in the NGINX Plus K/V store.

  • Token Recommandation

    When Using Do Don't
    ID Token - Assume the user is authenticated - Call an API
    - Get user profile data - Check if the client is allowed to access something.
    Access Token - Call an API - Inspect its content on the client
    - Check if the client is allowed to access something
    - Inspect its content on the server side

    courtesy: ID Token and Access Token: What's the Difference?

Acceptance Criteria:

  • Enhance the NJS Code to capture the access_token sent by the IdP.
  • Store the access_token in the k/v store as same as we store id_token and refresh_token

Compatibility:

  • This issue will not block the existing features as there would be no change of variables, and this is just to add features.

Question: OIDC optional per location or source IP

Is it possible to enforce client OIDC authentication only for some location(s)? In my use case, I would like to require OIDC authentication for browser users but allow calls to location /rest to pass through to the upstream server without redirect.

Return 401 instead of redirecting to authorize endpoint

Hi Team,

We have a web application protected behind the NGINX OpenID Connect RP. Part of the web application is an URI consisting of APIs, for example one of the API is /webapp/api/search.

Is it possible to configure Nginx such that if the session is not authenticated or if the session has timed out, this particular URI /webapp/api/ returns a HTTP code of 401 instead of redirecting to the IDP's authorize endpoint?

In Apache mod_auth_openidc module, it is possible to achieve this using the OIDCUnAuthAction parameter
https://github.com/zmartzone/mod_auth_openidc/blob/master/auth_openidc.conf#L853

Is there a setting or Parameter in Nginx that can achieve this?

Enhance custom query params for OIDC authZ endpoint

As a Product Manager,

I want to more flexibly configure query parameters for the OIDC authZ endpoint. So customers can customize the OIDC endpoints to pass vendor specific query parameters to complete their flow. For example, Azure AD B2B expects to send a special query param called resource-id to be passed to its authorization endpoint.

In addition to that, I want to synchronize the variable name between NGINX Plus and NGINX Management Suite.

  • NGINX Plus OIDC: $oidc_authz_extra_args is merged (Dec/8/2022)
  • NGINX Management Suite: $oidc_authz_query_params is released (Jul/20/2022)

AC:

  • Refactor and enhance the existing reference implementation and the latest PR to support following options:
    • option 1. Use built-in params
    • option 2. Extend extra params after the built-in params
    • option 3. Replace built-in params with custom params
  • Revise the name from $oidc_authz_extra_args to $oidc_authz_query_params.
  • Add key/values (e.g., $pkce_code_challenge, $nonce_hash) that can be configured as query params by customers for the OIDC authZ endpoint.

Issue with special character handling in redirect URI after authentication

Problem

An issue arises when the original request's query string contains certain special characters, such as semicolons (;). This issue becomes apparent after authentication when the user agent is redirected back to the application, resulting in a truncated query string in the redirect URI.

Technical details

The core of the issue is the absence of URI encoding for the auth_redir cookie value. When the Set-Cookie header is set with a URI that includes a semicolon, the browser misinterprets the semicolon as a part of the cookie's attribute delimiter. This results in the browser truncating the cookie value at the point of the special character. Consequently, when the user is redirected back post-authentication, we retrieve a truncated version of the original URI from the auth_redir cookie, leading to incorrect or incomplete redirection.

Proposed solution

To prevent this issue, we need to URI-encode the original request before setting it as the cookie value.

Having issues while trying to protect server with OIDC RP

Hi

When i am trying to protect the server (instead of location) with OIDC RP reference implementation , login flow is not kicking off. It works fine if we have the following defined in the location block works fine as mentioned in the reference implementation
error_page 401 = @do_oidc_flow;
auth_jwt "authz" token=$session_jwt;
auth_jwt_key_request /_jwks_uri; # Enable when using filename

Since the auth_jwt is defined in the server block , the named location is also being protected . I was directed to use
auth_jwt off;
in the openid_connect.server_conf

Can we fix the openid_connect.server_conf to have the same ? though the reference implementation doesn't have this challenge, it would helpful if anyone wanted to protect the whole server to with the given config.

Change needed to support OneLogin OIDCv2

OneLogin is deprecating their v1 OIDC endpoints. They have an upgrade guide but this doesn't tell you that the offline_access scope is not supported.

That's mentioned here:

Using this scope with Implicit or Authorization Code flow will cause an error.

The following patch seems to resolve this:

diff --git a/openid_connect.server_conf b/openid_connect.server_conf
index 95421d2..15f3391 100644
--- a/openid_connect.server_conf
+++ b/openid_connect.server_conf
@@ -7,7 +7,9 @@

             # This URL should work for most OpenID Connect providers.
             # Adjust the scope or state values as required (offline_access enables refresh tokens)
-            return 302 "$oidc_authz_endpoint?response_type=code&scope=openid+profile+email+offline_access&client_id=$oidc_client&state=0&redirect_uri=$scheme://$host:$server_port$redir_location&nonce=$requestid_hash";
+            # bsima: remove offline_access because it OneLogin's OIDCv2 doesn't
+            # like it? https://developers.onelogin.com/openid-connect/scopes
+            return 302 "$oidc_authz_endpoint?response_type=code&scope=openid+profile+email&client_id=$oidc_client&state=0&redirect_uri=$scheme://$host:$server_port$redir_location&nonce=$requestid_hash";
         }

         # We have a refresh token so perform refresh operation
@@ -39,7 +41,7 @@

         # Catch errors from oidcCodeExchange()
         # 500 = token validation error, 502 = error from IdP, 504 = IdP timeout
-        error_page 500 502 504 @oidc_error;
+        error_page 500 502 504 @oidc_error;

         access_log /var/log/nginx/oidc_auth.log main_jwt;
         error_log  /var/log/nginx/oidc_error.log debug;
@@ -112,5 +114,5 @@
         allow 127.0.0.1; # Only the NGINX host may call the NIGNX Plus API
         deny all;
     }
-
+
 # vim: syntax=nginx

I'm not sure if this patch should be upstreamed or not, I'm just sharing this in case anyone else runs into this problem.

access token and new endpoints (/login, /userinfo, /v2/logout)

Background:

  • Current NJS implementation disregard the access_token that is being sent by the IdP and only uses the id_token to get stored in the NGINX Plus K/V store.

  • Token Recommandation

    When Using Do Don't
    ID Token - Assume the user is authenticated - Call an API
    - Get user profile data - Check if the client is allowed to access something.
    Access Token - Call an API - Inspect its content on the client
    - Check if the client is allowed to access something
    - Inspect its content on the server side

    courtesy: ID Token and Access Token: What's the Difference?

  • Current NJS implementation doesn’t have /login and /userinfo endpoints for client apps (SPA) to interact with.

  • Client Apps require /login function as part of relying party when a user clicks on login button from the landing page.

  • Client Apps require /userinfo function as part of relying party when a user wants to verify the session cookie created by NGINX Plus is still valid or to get some user info about users which is needed for the Client Apps.

  • The existing /logout function is required to extend the sign-off function on the IdP's end_session_endpoint. Afterwards the NGINX Plus' logout redirection URI (which is redirected by IdP after successful logout from IdP) can clear session cookies and redirect to the either original landing page or a custom logout page.

Acceptance Criteria:

  • Enhance the NJS Code to capture the access_token sent by the IdP.

  • Store the access_token in the k/v store as same as we store id_token and refresh_token

  • Add /userinfo endpoint:

    • Add a map variable of $oidc_userinfo_endpoint as same as authz and token endpoints here (openid_connect_configuration.conf) .
    • Expose /userinfo endpoint here(openid_connect.server_conf) in a location block of NGINX Plus to interact with IdP's userinfo_endpoint which is defined in the endpoint ofwell-known/openid-configuration.
    • The nginx location block should proxy to the IdP’s userinfo_endpoint by adding access_token as a bearer token.
      Authorization : Bearer <access_token>
      
    • The response coming from IdP should be returned back to the caller as it is.
  • Expose /login endpoint:

    • Expose the /login endpoint as a location block here (openid_connect.server_conf)
    • Proxy it to the IdP's authorization_endpoint configured in the map variable of $oidc_authz_endpoint in (openid_connect_configuration.conf).
    • This would outsource the login function to IdP as its configured.
  • Expose /v2/logout endpoint:

    • Expose the /v2/logout endpoint as a location block here (openid_connect.server_conf)
    • Add a map variable of $oidc_end_session_endpoint as same as authz and token endpoints here (openid_connect_configuration.conf) .
    • Proxy it to the IdP's end_session_endpoint to finish the session by IdP.
  • Expose /v2/_logout endpoint:

    • Expose /v2/_logout endpoint which is a callback from IdP as a location block here (openid_connect.server_conf) to handle the following sequences.
        1. Redirected by IdP when IdP successfully finished the session.
        1. NGINX Plus: Clear session cookies.
        1. NGINX Plus: Redirect to either the original landing page or the custom logout page by calling
    • Add a map of $post_logout_return_uri: After the successful logout from the IdP, NGINX Plus calls this URI to redirect to either the original page or a custom logout page. The default is original page based on the configuration of $redirect_base.

Compatibility:

  • This issue will not block the existing features as there would be no change of variables, and this is just to add features.

Exceptions:

  • The docs will be enhanced with a separate PR.
  • The demo as a quick start guide will be provided with a separate PR.

Allow extra args to be provided to the OIDC auth endpoint

Some OIDC Identity Providers provide extended capabilities by adding extra query string arguments to the authentication request.

Specifically, Keycloak allows a default identity provider to be specified by adding a "kc_idp_hint" parameter to the authentication request (see https://www.keycloak.org/docs/latest/server_admin/#_client_suggested_idp).

It would be great to be able to include extra request arguments provided by the configuration.

Proposed changes in PR #67.

Add support for aud claim with more than one element

There is no support for aud claim with more than one element.

The Client MUST validate that the aud (audience) Claim contains its client_id value registered at the Issuer identified by the iss (issuer) Claim as an audience. The aud (audience) Claim MAY contain an array with more than one element. The ID Token MUST be rejected if the ID Token does not list the Client as a valid audience, or if it contains additional audiences not trusted by the Client.

http://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation

How to get User Information in Nginx log?

Hi, thank you for you sharing, and may I ask a question?
When I use this module, I also want to get the login information of users.
We found there is an api in module of apache, which can help us to get the userinfo in Apache log.
Does this nginx module have such a function?
Thanks in advance.

add support to encrypt auth_token cookie

Do we have support for encryption of cookie content & decrypting it back before performing token validation?
The ask is important as with existing infra the cookies are not encrypted & thus this brings in security issues with oauth oidc flows.
By tweaking into the existing code flows, we can do the encrypt/decrypt stuff but raising this question here might get better outcome than doing a tweak. As you have already have had something in your plate or have some dynamic module to serve the purpose.

Does this support Nginx oss?

Does this module support for oss too? may be a part of it?
Also i would really like to know how do i protect my API (GET, POST ) calls with this approach?
Here is the idea!

  1. Client calls nginx get the access_token & id_token
  2. Add those tokens to subsequent calls in client POST | GET req

Will that be possible with this module? and the same can be validated when the POST | GET req hits nginx.

different config per Location

How is it possible to configure openid that per location is used different configuration :

location /a
location /a/autharea
config1

location /b
location /b/autharea
config2

audience check failed for array type field

With nginx plus openid connect feature, if the id_token has "aud" field of type array (json array object). It fails to validate the token & returns failure.
Error Log: 2019/01/04 19:55:11 [error] 3435#3435: *2 js: OIDC ID Token validation error: missing claim(s) aud

As a workaround, if i disable the audience check from openid_connect.js script everything works fine.

Eg:
"aud": [

"​https://identity.cloud.com/",
"e46481793d7744178d5df02d2e7f9a3e"

],

Note: Facing this issue with IDCS (https://cloud.oracle.com/idcs)

Error in configure script when supplying secret

When I use the configure script and supply a host, auth_jwt_key value, client_id, and client_secret, the client secret would be configured as 0. After doing some debugging it appears the issue is in the LINE calculation during the final for loop. In the openid_connect_configuration.conf file, you have the oidc_pkce_enable parameter declared above the oidc_client_secret parameter. However in the for loop, the oidc_client_secret parameter is called before the oidc_pkce_enable parameter. In that LINE calculation, you perform a grep with A10 and then look for an exisiting hostname. Because the client_secret is after the pkce_enable, when you attempt to setup the pkce_enable, the client_seceret hostname is visible in the A10 grep and uses that line.

It seems a simple fix is to either change the order in the for loop to have the pkce_enable before the client_secret or just move the pkce_enable declaration beneath the client_secret declaration.

Use of optionals parameters for specifics IDP

I use keycloak with this script and I want to pass and use specific keycloak parameter kc_idp_hint. This parameter is given client side according keycloak documentation (i.e : https://www.keycloak.org/docs/latest/server_admin/#default_identity_provider!) to automatically select IDP to use.

Is there a way to pass optional parameters (kc_idp_hint and kc_idp_hint value) to authZargs in opened_connect.js line 247 ?

I would prefer not to modify or fork the script in order to be able to apply future updates.

Configuration script: user info and end session endpoint

Background:

  • The configure.sh script currently supports to complete the following configuration if an IdP supports OpenID Connect Discovery (usually at the URI /.well-known/openid-configuration).
    • Obtain the URL for jwks_uri or download the JWK file to your NGINX Plus instance
    • Obtain the URL for the authorization endpoint
    • Obtain the URL for the token endpoint
  • NGINX Plus OIDC additionally exposed the endpoints of user information and logout in the PR. Hence they need to be configured by using configure.sh.

Acceptance Criteria:

  • Supports configure.sh to additionally complete the following configuration.
    • Obtain the URL for the user information endpoint of IdP.
    • Obtain the URL for the end session endpoint of IdP.
  • Update README.md.

How to change the parameter of timeout ?

Hi
When we use this OIDC, we found the oidc will become timeout,
but we can not change the Expiration time neither know when it become expire.
We set something like this:
keyval_zone zone=oidc_id_tokens:1M state=/var/log/nginx/oidc_id_tokens.json timeout=1h sync;
keyval_zone zone=refresh_tokens:1M state=/var/log/nginx/refresh_tokens.json timeout=1h sync;
But it doesn't work.
It would helpful if someone could tell me how to set the Expiration time.

_codexch uses default Client ID / Secret

Hi,

I have a requirement to protect different URI on the same reverse proxy with different set of Client ID/Secret and Scopes, for example:

  • /app1 -> ClientID: AAA / ClientSecret: AAA / Scope: scope1
  • /app2 -> ClientID: BBB / ClientSecret: BBB / Scope: scope2

I followed Issue #27 to map using $uri instead of $host

openid_connect_configuration.conf

# 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 {
    rp.company.com https://sso.company.com/oxauth/restv1/authorize;

    default "http://sso.company.com/oxauth/restv1/authorize";
    #www.example.com "https://my-idp/oauth2/v1/authorize";
}

map $host $oidc_token_endpoint {
    rp.company.com https://sso.company.com/oxauth/restv1/token;

    default "https://sso.company.com/oxauth/restv1/token";
}

map $host $oidc_jwt_keyfile {
    rp.company.com https://sso.company.com/oxauth/restv1/jwks;

    default "http://sso.company.com/oxauth/restv1/jwks";
}

map $uri $oidc_client {
    ~^/htvar/(.*)$ client-id-aaa;

    default "my-client-id";
}


map $host $oidc_pkce_enable {
    default 0;
}

map $uri $oidc_client_secret {
    ~^/htvar/(.*)$ "client-secret-aaa";

    default "my-client-secret";
}


map $uri $oidc_scopes {
    ~^/htvar/(.*)$ "openid+profile+email+user_name+user_role+user_type";
    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 {
    rp.company.com hmac_key;

    # This should be unique for every NGINX instance/cluster
    default "ChangeMe";
}

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=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 $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_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

When I access the protected URI, I am prompted to login at my SSO server and redirected back to the callback URI _codexch.
However, the _codexch throws a NGINX / OpenID Connect login failure error.
From the SSO server, I am seeing that the OIDC is attempting to authenticate with my-client-id and my-client-secret, which is the default value in the openid_connect_configuration.conf file.

It seems like it is not picking up the Client ID/Secret for the URI.

The debug output of nginx is:

2021/12/08 16:29:25 [debug] 21315#21315: accept on 0.0.0.0:443, ready: 0
2021/12/08 16:29:25 [debug] 21314#21314: accept on 0.0.0.0:443, ready: 0
2021/12/08 16:29:25 [debug] 21315#21315: posix_memalign: 0000560F14FCEF60:512 @16
2021/12/08 16:29:25 [debug] 21314#21314: accept() not ready (11: Resource temporarily unavailable)
2021/12/08 16:29:25 [debug] 21315#21315: *1 accept: 192.168.0.91:55291 fd:16
2021/12/08 16:29:25 [debug] 21315#21315: *1 event timer add: 16: 60000:429931535
2021/12/08 16:29:25 [debug] 21315#21315: *1 reusable connection: 1
2021/12/08 16:29:25 [debug] 21315#21315: *1 epoll add event: fd:16 op:1 ev:80002001
2021/12/08 16:29:25 [debug] 21315#21315: *1 http check ssl handshake
2021/12/08 16:29:25 [debug] 21315#21315: *1 http recv(): 1
2021/12/08 16:29:25 [debug] 21315#21315: *1 https ssl handshake: 0x16
2021/12/08 16:29:25 [debug] 21315#21315: *1 tcp_nodelay
2021/12/08 16:29:25 [debug] 21315#21315: *1 reusable connection: 0
2021/12/08 16:29:25 [debug] 21315#21315: *1 SSL server name: "rp.company.com"
2021/12/08 16:29:25 [debug] 21315#21315: *1 SSL ALPN supported by client: h2
2021/12/08 16:29:25 [debug] 21315#21315: *1 SSL ALPN supported by client: http/1.1
2021/12/08 16:29:25 [debug] 21315#21315: *1 SSL ALPN selected: http/1.1
2021/12/08 16:29:25 [debug] 21315#21315: *1 SSL_do_handshake: -1
2021/12/08 16:29:25 [debug] 21315#21315: *1 SSL_get_error: 2
2021/12/08 16:29:25 [debug] 21315#21315: *1 SSL handshake handler: 0
2021/12/08 16:29:25 [debug] 21315#21315: *1 SSL_do_handshake: 0
2021/12/08 16:29:25 [debug] 21315#21315: *1 SSL_get_error: 1
2021/12/08 16:29:25 [info] 21315#21315: *1 SSL_do_handshake() failed (SSL: error:14094416:SSL routines:ssl3_read_bytes:sslv3 alert certificate unknown:SSL alert number 46) while SSL handshaking, client: 192.168.0.91, server: 0.0.0.0:443
2021/12/08 16:29:25 [debug] 21315#21315: *1 close http connection: 16
2021/12/08 16:29:25 [debug] 21315#21315: *1 event timer del: 16: 429931535
2021/12/08 16:29:25 [debug] 21315#21315: *1 reusable connection: 0
2021/12/08 16:29:25 [debug] 21315#21315: *1 free: 0000560F14FCEF60, unused: 121
2021/12/08 16:29:25 [debug] 21314#21314: accept on 0.0.0.0:443, ready: 0
2021/12/08 16:29:25 [debug] 21315#21315: accept on 0.0.0.0:443, ready: 0
2021/12/08 16:29:25 [debug] 21315#21315: accept() not ready (11: Resource temporarily unavailable)
2021/12/08 16:29:25 [debug] 21314#21314: posix_memalign: 0000560F14FCEF60:512 @16
2021/12/08 16:29:25 [debug] 21314#21314: *2 accept: 192.168.0.91:55292 fd:16
2021/12/08 16:29:25 [debug] 21314#21314: *2 event timer add: 16: 60000:429931575
2021/12/08 16:29:25 [debug] 21314#21314: *2 reusable connection: 1
2021/12/08 16:29:25 [debug] 21314#21314: *2 epoll add event: fd:16 op:1 ev:80002001
2021/12/08 16:29:25 [debug] 21314#21314: *2 http check ssl handshake
2021/12/08 16:29:25 [debug] 21314#21314: *2 http recv(): 1
2021/12/08 16:29:25 [debug] 21314#21314: *2 https ssl handshake: 0x16
2021/12/08 16:29:25 [debug] 21314#21314: *2 tcp_nodelay
2021/12/08 16:29:25 [debug] 21314#21314: *2 reusable connection: 0
2021/12/08 16:29:25 [debug] 21314#21314: *2 SSL server name: "rp.company.com"
2021/12/08 16:29:25 [debug] 21314#21314: *2 SSL ALPN supported by client: h2
2021/12/08 16:29:25 [debug] 21314#21314: *2 SSL ALPN supported by client: http/1.1
2021/12/08 16:29:25 [debug] 21314#21314: *2 SSL ALPN selected: http/1.1
2021/12/08 16:29:25 [debug] 21314#21314: *2 SSL_do_handshake: -1
2021/12/08 16:29:25 [debug] 21314#21314: *2 SSL_get_error: 2
2021/12/08 16:29:25 [debug] 21314#21314: *2 SSL handshake handler: 0
2021/12/08 16:29:25 [debug] 21314#21314: *2 SSL_do_handshake: 1
2021/12/08 16:29:25 [debug] 21314#21314: *2 SSL: TLSv1.2, cipher: "ECDHE-RSA-AES128-GCM-SHA256 TLSv1.2 Kx=ECDH Au=RSA Enc=AESGCM(128) Mac=AEAD"
2021/12/08 16:29:25 [debug] 21314#21314: *2 reusable connection: 1
2021/12/08 16:29:25 [debug] 21314#21314: *2 http wait request handler
2021/12/08 16:29:25 [debug] 21314#21314: *2 malloc: 0000560F15052B80:1024
2021/12/08 16:29:25 [debug] 21314#21314: *2 SSL_read: 764
2021/12/08 16:29:25 [debug] 21314#21314: *2 SSL_read: -1
2021/12/08 16:29:25 [debug] 21314#21314: *2 SSL_get_error: 2
2021/12/08 16:29:25 [debug] 21314#21314: *2 reusable connection: 0
2021/12/08 16:29:25 [debug] 21314#21314: *2 posix_memalign: 0000560F14F12A60:4096 @16
2021/12/08 16:29:25 [debug] 21314#21314: *2 http process request line
2021/12/08 16:29:25 [debug] 21314#21314: *2 http request line: "GET /htvar/ugly.jsp HTTP/1.1"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http uri: "/htvar/ugly.jsp"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http args: ""
2021/12/08 16:29:25 [debug] 21314#21314: *2 http exten: "jsp"
2021/12/08 16:29:25 [debug] 21314#21314: *2 posix_memalign: 0000560F1504E2C0:4096 @16
2021/12/08 16:29:25 [debug] 21314#21314: *2 http process request header line
2021/12/08 16:29:25 [debug] 21314#21314: *2 http header: "Host: rp.company.com"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http header: "Connection: keep-alive"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http header: "sec-ch-ua: " Not A;Brand";v="99", "Chromium";v="96", "Google Chrome";v="96""
2021/12/08 16:29:25 [debug] 21314#21314: *2 http header: "sec-ch-ua-mobile: ?0"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http header: "sec-ch-ua-platform: "Windows""
2021/12/08 16:29:25 [debug] 21314#21314: *2 http header: "DNT: 1"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http header: "Upgrade-Insecure-Requests: 1"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http header: "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.45 Safari/537.36"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http header: "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http header: "Sec-Fetch-Site: none"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http header: "Sec-Fetch-Mode: navigate"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http header: "Sec-Fetch-User: ?1"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http header: "Sec-Fetch-Dest: document"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http header: "Accept-Encoding: gzip, deflate, br"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http header: "Accept-Language: en-SG,en;q=0.9"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http header: "Cookie: auth_redir=/htvar/ugly.jsp; auth_nonce=f3bfeb7bcc2dc7769eb92b9c81403879"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http header done
2021/12/08 16:29:25 [debug] 21314#21314: *2 event timer del: 16: 429931575
2021/12/08 16:29:25 [debug] 21314#21314: *2 generic phase: 0
2021/12/08 16:29:25 [debug] 21314#21314: *2 rewrite phase: 1
2021/12/08 16:29:25 [debug] 21314#21314: *2 http script value: "NGINX / OpenID Connect login failure
"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http script set $internal_error_message
2021/12/08 16:29:25 [debug] 21314#21314: *2 http script value: ""
2021/12/08 16:29:25 [debug] 21314#21314: *2 http script set $pkce_id
2021/12/08 16:29:25 [debug] 21314#21314: *2 http script value: "/_codexch"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http script set $redir_location
2021/12/08 16:29:25 [debug] 21314#21314: *2 test location: "/"
2021/12/08 16:29:25 [debug] 21314#21314: *2 test location: "_refresh"
2021/12/08 16:29:25 [debug] 21314#21314: *2 test location: "htvar/"
2021/12/08 16:29:25 [debug] 21314#21314: *2 using configuration "/htvar/"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http cl:-1 max:1048576
2021/12/08 16:29:25 [debug] 21314#21314: *2 rewrite phase: 3
2021/12/08 16:29:25 [debug] 21314#21314: *2 post rewrite phase: 4
2021/12/08 16:29:25 [debug] 21314#21314: *2 generic phase: 5
2021/12/08 16:29:25 [debug] 21314#21314: *2 generic phase: 6
2021/12/08 16:29:25 [debug] 21314#21314: *2 generic phase: 7
2021/12/08 16:29:25 [debug] 21314#21314: *2 access phase: 8
2021/12/08 16:29:25 [debug] 21314#21314: *2 access phase: 9
2021/12/08 16:29:25 [debug] 21314#21314: *2 auth jwt request handler
2021/12/08 16:29:25 [debug] 21314#21314: *2 parse header: "Cookie: auth_redir=/htvar/ugly.jsp; auth_nonce=f3bfeb7bcc2dc7769eb92b9c81403879"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http finalize request: 401, "/htvar/ugly.jsp?" a:1, c:1
2021/12/08 16:29:25 [debug] 21314#21314: *2 http special response: 401, "/htvar/ugly.jsp?"
2021/12/08 16:29:25 [debug] 21314#21314: *2 test location: "@do_oidc_flow"
2021/12/08 16:29:25 [debug] 21314#21314: *2 using location: @do_oidc_flow "/htvar/ugly.jsp?"
2021/12/08 16:29:25 [debug] 21314#21314: *2 rewrite phase: 3
2021/12/08 16:29:25 [debug] 21314#21314: *2 post rewrite phase: 4
2021/12/08 16:29:25 [debug] 21314#21314: *2 generic phase: 5
2021/12/08 16:29:25 [debug] 21314#21314: *2 generic phase: 6
2021/12/08 16:29:25 [debug] 21314#21314: *2 generic phase: 7
2021/12/08 16:29:25 [debug] 21314#21314: *2 access phase: 8
2021/12/08 16:29:25 [debug] 21314#21314: *2 access phase: 9
2021/12/08 16:29:25 [debug] 21314#21314: *2 access phase: 10
2021/12/08 16:29:25 [debug] 21314#21314: *2 access phase: 11
2021/12/08 16:29:25 [debug] 21314#21314: *2 post access phase: 12
2021/12/08 16:29:25 [debug] 21314#21314: *2 generic phase: 13
2021/12/08 16:29:25 [debug] 21314#21314: *2 generic phase: 14
2021/12/08 16:29:25 [debug] 21314#21314: *2 add cleanup: 0000560F1504E9B0
2021/12/08 16:29:25 [debug] 21314#21314: *2 http map started
2021/12/08 16:29:25 [debug] 21314#21314: *2 http script var: "rp.company.com"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http map: "rp.company.com" "https://sso.company.com/oxauth/restv1/authorize"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http map started
2021/12/08 16:29:25 [debug] 21314#21314: *2 http script var: "rp.company.com"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http map: "rp.company.com" "https://sso.company.com/oxauth/restv1/authorize"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http map started
2021/12/08 16:29:25 [debug] 21314#21314: *2 http script var: "/htvar/ugly.jsp"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http map: "/htvar/ugly.jsp" "openid+profile+email+user_name+user_role+user_type"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http map started
2021/12/08 16:29:25 [debug] 21314#21314: *2 http script var: "/htvar/ugly.jsp"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http map: "/htvar/ugly.jsp" "openid+profile+email+user_name+user_role+user_type"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http map started
2021/12/08 16:29:25 [debug] 21314#21314: *2 http script var: "rp.company.com"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http map: "rp.company.com" "hmac_key"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http map started
2021/12/08 16:29:25 [debug] 21314#21314: *2 http script var: "rp.company.com"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http map: "rp.company.com" "hmac_key"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http map started
2021/12/08 16:29:25 [debug] 21314#21314: *2 http map started
2021/12/08 16:29:25 [debug] 21314#21314: *2 http script var: "https"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http map: "" "https"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http script var: "https"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http map: "https" "Path=/; SameSite=lax; HttpOnly; Secure;"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http map started
2021/12/08 16:29:25 [debug] 21314#21314: *2 http script var: "rp.company.com"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http map: "rp.company.com" "https://sso.company.com/oxauth/restv1/authorize"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http map started
2021/12/08 16:29:25 [debug] 21314#21314: *2 http script var: "rp.company.com"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http map: "rp.company.com" "hmac_key"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http map started
2021/12/08 16:29:25 [debug] 21314#21314: *2 http script var: "/htvar/ugly.jsp"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http map: "/htvar/ugly.jsp" "openid+profile+email+user_name+user_role+user_type"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http map started
2021/12/08 16:29:25 [debug] 21314#21314: *2 http script var: "/htvar/ugly.jsp"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http map: "/htvar/ugly.jsp" "client-id-aaa"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http map started
2021/12/08 16:29:25 [debug] 21314#21314: *2 http script var: "https"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http script copy: "://"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http script var: "rp.company.com"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http script copy: ":"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http script var: "443"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http map: "" "https://rp.company.com:443"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http map started
2021/12/08 16:29:25 [debug] 21314#21314: *2 http script var: "rp.company.com"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http map: "rp.company.com" "0"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http finalize request: 302, "/htvar/ugly.jsp?" a:1, c:3
2021/12/08 16:29:25 [debug] 21314#21314: *2 http special response: 302, "/htvar/ugly.jsp?"
2021/12/08 16:29:25 [debug] 21314#21314: *2 HTTP/1.1 302 Moved Temporarily
Server: nginx/1.21.3
Date: Wed, 08 Dec 2021 08:29:25 GMT
Content-Type: text/html
Content-Length: 145
Connection: keep-alive
WWW-Authenticate: Bearer realm=""
Set-Cookie: auth_redir=/htvar/ugly.jsp; Path=/; SameSite=lax; HttpOnly; Secure;
Set-Cookie: auth_nonce=85e6aa6169ae466048c5672482f4d03d; Path=/; SameSite=lax; HttpOnly; Secure;
Location: https://sso.company.com/oxauth/restv1/authorize?response_type=code&scope=openid+profile+email+user_name+user_role+user_type&client_id=client-id-aaa&redirect_uri=https://rp.company.com:443/_codexch&nonce=TfWwxHwliyU-T3kbxPrbc_FKqupcfcDlkuxdiLrCawA&state=0

2021/12/08 16:29:25 [debug] 21314#21314: *2 write new buf t:1 f:0 0000560F1504ED50, pos 0000560F1504ED50, size: 670 file: 0, size: 0
2021/12/08 16:29:25 [debug] 21314#21314: *2 http write filter: l:0 f:0 s:670
2021/12/08 16:29:25 [debug] 21314#21314: *2 http output filter "/htvar/ugly.jsp?"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http copy filter: "/htvar/ugly.jsp?"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http postpone filter "/htvar/ugly.jsp?" 0000560F1504F138
2021/12/08 16:29:25 [debug] 21314#21314: *2 write old buf t:1 f:0 0000560F1504ED50, pos 0000560F1504ED50, size: 670 file: 0, size: 0
2021/12/08 16:29:25 [debug] 21314#21314: *2 write new buf t:0 f:0 0000000000000000, pos 0000560F142A8820, size: 92 file: 0, size: 0
2021/12/08 16:29:25 [debug] 21314#21314: *2 write new buf t:0 f:0 0000000000000000, pos 0000560F142A8C00, size: 53 file: 0, size: 0
2021/12/08 16:29:25 [debug] 21314#21314: *2 http write filter: l:1 f:0 s:815
2021/12/08 16:29:25 [debug] 21314#21314: *2 http write filter limit 0
2021/12/08 16:29:25 [debug] 21314#21314: *2 posix_memalign: 0000560F15052670:512 @16
2021/12/08 16:29:25 [debug] 21314#21314: *2 malloc: 0000560F15055110:16384
2021/12/08 16:29:25 [debug] 21314#21314: *2 SSL buf copy: 670
2021/12/08 16:29:25 [debug] 21314#21314: *2 SSL buf copy: 92
2021/12/08 16:29:25 [debug] 21314#21314: *2 SSL buf copy: 53
2021/12/08 16:29:25 [debug] 21314#21314: *2 SSL to write: 815
2021/12/08 16:29:25 [debug] 21314#21314: *2 SSL_write: 815
2021/12/08 16:29:25 [debug] 21314#21314: *2 http write filter 0000000000000000
2021/12/08 16:29:25 [debug] 21314#21314: *2 http copy filter: 0 "/htvar/ugly.jsp?"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http finalize request: 0, "/htvar/ugly.jsp?" a:1, c:3
2021/12/08 16:29:25 [debug] 21314#21314: *2 http request count:3 blk:0
2021/12/08 16:29:25 [debug] 21314#21314: *2 http finalize request: -4, "/htvar/ugly.jsp?" a:1, c:2
2021/12/08 16:29:25 [debug] 21314#21314: *2 http request count:2 blk:0
2021/12/08 16:29:25 [debug] 21314#21314: *2 http finalize request: -4, "/htvar/ugly.jsp?" a:1, c:1
2021/12/08 16:29:25 [debug] 21314#21314: *2 set http keepalive handler
2021/12/08 16:29:25 [debug] 21314#21314: *2 http close request
2021/12/08 16:29:25 [debug] 21314#21314: *2 http log handler
2021/12/08 16:29:25 [debug] 21314#21314: *2 http session log handler
2021/12/08 16:29:25 [debug] 21314#21314: *2 run cleanup: 0000560F1504E9B0
2021/12/08 16:29:25 [debug] 21314#21314: *2 free: 0000560F14F12A60, unused: 3
2021/12/08 16:29:25 [debug] 21314#21314: *2 free: 0000560F1504E2C0, unused: 48
2021/12/08 16:29:25 [debug] 21314#21314: *2 free: 0000560F15052B80
2021/12/08 16:29:25 [debug] 21314#21314: *2 hc free: 0000000000000000
2021/12/08 16:29:25 [debug] 21314#21314: *2 hc busy: 0000000000000000 0
2021/12/08 16:29:25 [debug] 21314#21314: *2 free: 0000560F15055110
2021/12/08 16:29:25 [debug] 21314#21314: *2 reusable connection: 1
2021/12/08 16:29:25 [debug] 21314#21314: *2 event timer add: 16: 65000:429936588
2021/12/08 16:29:25 [debug] 21314#21314: *2 http keepalive handler
2021/12/08 16:29:25 [debug] 21314#21314: *2 malloc: 0000560F15052B80:1024
2021/12/08 16:29:25 [debug] 21314#21314: *2 SSL_read: 1024
2021/12/08 16:29:25 [debug] 21314#21314: *2 SSL_read: avail:0
2021/12/08 16:29:25 [debug] 21314#21314: *2 reusable connection: 0
2021/12/08 16:29:25 [debug] 21314#21314: *2 posix_memalign: 0000560F14F12A60:4096 @16
2021/12/08 16:29:25 [debug] 21314#21314: *2 event timer del: 16: 429936588
2021/12/08 16:29:25 [debug] 21314#21314: *2 http process request line
2021/12/08 16:29:25 [debug] 21314#21314: *2 http request line: "GET /_codexch?code=b8bbd874-6eab-4282-b755-8501025ba68e&scope=user_role+user_type+openid+user_name+profile+email&session_id=31be09e0-b503-4928-9fa5-c2d706cdde4b&state=0&session_state=838d83a219f4e96aa1714452d1e6c5ff01c6cb12cfa69077f7e67b689f8c11bb.ac39bee8-bff6-4853-8d7c-18454f6f14f7 HTTP/1.1"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http uri: "/_codexch"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http args: "code=b8bbd874-6eab-4282-b755-8501025ba68e&scope=user_role+user_type+openid+user_name+profile+email&session_id=31be09e0-b503-4928-9fa5-c2d706cdde4b&state=0&session_state=838d83a219f4e96aa1714452d1e6c5ff01c6cb12cfa69077f7e67b689f8c11bb.ac39bee8-bff6-4853-8d7c-18454f6f14f7"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http exten: ""
2021/12/08 16:29:25 [debug] 21314#21314: *2 posix_memalign: 0000560F1504E2C0:4096 @16
2021/12/08 16:29:25 [debug] 21314#21314: *2 http process request header line
2021/12/08 16:29:25 [debug] 21314#21314: *2 http header: "Host: rp.company.com"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http header: "Connection: keep-alive"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http header: "DNT: 1"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http header: "Upgrade-Insecure-Requests: 1"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http header: "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.45 Safari/537.36"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http header: "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http header: "Sec-Fetch-Site: none"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http header: "Sec-Fetch-Mode: navigate"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http header: "Sec-Fetch-User: ?1"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http header: "Sec-Fetch-Dest: document"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http header: "sec-ch-ua: " Not A;Brand";v="99", "Chromium";v="96", "Google Chrome";v="96""
2021/12/08 16:29:25 [debug] 21314#21314: *2 http header: "sec-ch-ua-mobile: ?0"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http header: "sec-ch-ua-platform: "Windows""
2021/12/08 16:29:25 [debug] 21314#21314: *2 http header: "Accept-Encoding: gzip, deflate, br"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http header: "Accept-Language: en-SG,en;q=0.9"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http alloc large header buffer
2021/12/08 16:29:25 [debug] 21314#21314: *2 malloc: 0000560F1504F2D0:8192
2021/12/08 16:29:25 [debug] 21314#21314: *2 http large header alloc: 0000560F1504F2D0 8192
2021/12/08 16:29:25 [debug] 21314#21314: *2 http large header copy: 78
2021/12/08 16:29:25 [debug] 21314#21314: *2 SSL_read: 5
2021/12/08 16:29:25 [debug] 21314#21314: *2 SSL_read: -1
2021/12/08 16:29:25 [debug] 21314#21314: *2 SSL_get_error: 2
2021/12/08 16:29:25 [debug] 21314#21314: *2 http header: "Cookie: auth_redir=/htvar/ugly.jsp; auth_nonce=85e6aa6169ae466048c5672482f4d03d"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http header done
2021/12/08 16:29:25 [debug] 21314#21314: *2 generic phase: 0
2021/12/08 16:29:25 [debug] 21314#21314: *2 rewrite phase: 1
2021/12/08 16:29:25 [debug] 21314#21314: *2 http script value: "NGINX / OpenID Connect login failure
"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http script set $internal_error_message
2021/12/08 16:29:25 [debug] 21314#21314: *2 http script value: ""
2021/12/08 16:29:25 [debug] 21314#21314: *2 http script set $pkce_id
2021/12/08 16:29:25 [debug] 21314#21314: *2 http script value: "/_codexch"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http script set $redir_location
2021/12/08 16:29:25 [debug] 21314#21314: *2 test location: "/"
2021/12/08 16:29:25 [debug] 21314#21314: *2 test location: "_refresh"
2021/12/08 16:29:25 [debug] 21314#21314: *2 test location: "_jwks_uri"
2021/12/08 16:29:25 [debug] 21314#21314: *2 test location: "_id_token_validation"
2021/12/08 16:29:25 [debug] 21314#21314: *2 test location: "_codexch"
2021/12/08 16:29:25 [debug] 21314#21314: *2 using configuration "=/_codexch"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http cl:-1 max:1048576
2021/12/08 16:29:25 [debug] 21314#21314: *2 rewrite phase: 3
2021/12/08 16:29:25 [debug] 21314#21314: *2 post rewrite phase: 4
2021/12/08 16:29:25 [debug] 21314#21314: *2 generic phase: 5
2021/12/08 16:29:25 [debug] 21314#21314: *2 generic phase: 6
2021/12/08 16:29:25 [debug] 21314#21314: *2 generic phase: 7
2021/12/08 16:29:25 [debug] 21314#21314: *2 access phase: 8
2021/12/08 16:29:25 [debug] 21314#21314: *2 access phase: 9
2021/12/08 16:29:25 [debug] 21314#21314: *2 access phase: 10
2021/12/08 16:29:25 [debug] 21314#21314: *2 access phase: 11
2021/12/08 16:29:25 [debug] 21314#21314: *2 post access phase: 12
2021/12/08 16:29:25 [debug] 21314#21314: *2 generic phase: 13
2021/12/08 16:29:25 [debug] 21314#21314: *2 generic phase: 14
2021/12/08 16:29:25 [debug] 21314#21314: *2 add cleanup: 0000560F1504E8D8
2021/12/08 16:29:25 [debug] 21314#21314: *2 http map started
2021/12/08 16:29:25 [debug] 21314#21314: *2 http script var: "rp.company.com"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http map: "rp.company.com" "0"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http map started
2021/12/08 16:29:25 [debug] 21314#21314: *2 http script var: "/_codexch"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http map: "/_codexch" "my-client-secret"
2021/12/08 16:29:25 [debug] 21314#21314: *2 posix_memalign: 0000560F15043910:4096 @16
2021/12/08 16:29:25 [debug] 21314#21314: *2 http subrequest "/_token?code=b8bbd874-6eab-4282-b755-8501025ba68e&client_secret=my-client-secret"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http finalize request: -4, "/_codexch?code=b8bbd874-6eab-4282-b755-8501025ba68e&scope=user_role+user_type+openid+user_name+profile+email&session_id=31be09e0-b503-4928-9fa5-c2d706cdde4b&state=0&session_state=838d83a219f4e96aa1714452d1e6c5ff01c6cb12cfa69077f7e67b689f8c11bb.ac39bee8-bff6-4853-8d7c-18454f6f14f7" a:1, c:3
2021/12/08 16:29:25 [debug] 21314#21314: *2 http request count:3 blk:0
2021/12/08 16:29:25 [debug] 21314#21314: *2 http posted request: "/_token?code=b8bbd874-6eab-4282-b755-8501025ba68e&client_secret=my-client-secret"
2021/12/08 16:29:25 [debug] 21314#21314: *2 rewrite phase: 1
2021/12/08 16:29:25 [debug] 21314#21314: *2 http script value: "NGINX / OpenID Connect login failure
"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http script set $internal_error_message
2021/12/08 16:29:25 [debug] 21314#21314: *2 http script value: ""
2021/12/08 16:29:25 [debug] 21314#21314: *2 http script set $pkce_id
2021/12/08 16:29:25 [debug] 21314#21314: *2 http script value: "/_codexch"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http script set $redir_location
2021/12/08 16:29:25 [debug] 21314#21314: *2 test location: "/"
2021/12/08 16:29:25 [debug] 21314#21314: *2 test location: "_refresh"
2021/12/08 16:29:25 [debug] 21314#21314: *2 test location: "htvar/"
2021/12/08 16:29:25 [debug] 21314#21314: *2 test location: "api/"
2021/12/08 16:29:25 [debug] 21314#21314: *2 test location: "_token"
2021/12/08 16:29:25 [debug] 21314#21314: *2 using configuration "=/_token"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http cl:-1 max:1048576
2021/12/08 16:29:25 [debug] 21314#21314: *2 rewrite phase: 3
2021/12/08 16:29:25 [debug] 21314#21314: *2 post rewrite phase: 4
2021/12/08 16:29:25 [debug] 21314#21314: *2 generic phase: 5
2021/12/08 16:29:25 [debug] 21314#21314: *2 generic phase: 6
2021/12/08 16:29:25 [debug] 21314#21314: *2 generic phase: 7
2021/12/08 16:29:25 [debug] 21314#21314: *2 generic phase: 13
2021/12/08 16:29:25 [debug] 21314#21314: *2 generic phase: 14
2021/12/08 16:29:25 [debug] 21314#21314: *2 http map started
2021/12/08 16:29:25 [debug] 21314#21314: *2 http script var: "rp.company.com"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http map: "rp.company.com" "https://sso.company.com/oxauth/restv1/token"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http script var: "https://sso.company.com/oxauth/restv1/token"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http init upstream, client timer: 0
2021/12/08 16:29:25 [debug] 21314#21314: *2 epoll add event: fd:16 op:3 ev:80002005
2021/12/08 16:29:25 [debug] 21314#21314: *2 http map started
2021/12/08 16:29:25 [debug] 21314#21314: *2 http script var: "/_token"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http map: "/_token" "my-client-id"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http map started
2021/12/08 16:29:25 [debug] 21314#21314: *2 http map started
2021/12/08 16:29:25 [debug] 21314#21314: *2 http script var: "https"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http map: "" "https"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http script var: "https"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http script copy: "://"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http script var: "rp.company.com"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http script copy: ":"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http script var: "443"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http map: "" "https://rp.company.com:443"
2021/12/08 16:29:25 [debug] 21314#21314: *2 posix_memalign: 0000560F15055110:4096 @16
2021/12/08 16:29:25 [debug] 21314#21314: *2 http script copy: "Content-Type"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http script copy: "application/x-www-form-urlencoded"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http script copy: "Host"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http script var: "sso.company.com"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http script copy: "Connection"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http script copy: "close"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http script copy: "Content-Length"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http script var: "175"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http script copy: ""
2021/12/08 16:29:25 [debug] 21314#21314: *2 http proxy header: "DNT: 1"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http proxy header: "Upgrade-Insecure-Requests: 1"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http proxy header: "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.45 Safari/537.36"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http proxy header: "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http proxy header: "Sec-Fetch-Site: none"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http proxy header: "Sec-Fetch-Mode: navigate"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http proxy header: "Sec-Fetch-User: ?1"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http proxy header: "Sec-Fetch-Dest: document"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http proxy header: "sec-ch-ua: " Not A;Brand";v="99", "Chromium";v="96", "Google Chrome";v="96""
2021/12/08 16:29:25 [debug] 21314#21314: *2 http proxy header: "sec-ch-ua-mobile: ?0"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http proxy header: "sec-ch-ua-platform: "Windows""
2021/12/08 16:29:25 [debug] 21314#21314: *2 http proxy header: "Accept-Encoding: gzip, deflate, br"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http proxy header: "Accept-Language: en-SG,en;q=0.9"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http proxy header: "Cookie: auth_redir=/htvar/ugly.jsp; auth_nonce=85e6aa6169ae466048c5672482f4d03d"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http script copy: "grant_type=authorization_code&client_id="
2021/12/08 16:29:25 [debug] 21314#21314: *2 http script var: "my-client-id"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http script copy: "&"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http script var: "code=b8bbd874-6eab-4282-b755-8501025ba68e&client_secret=my-client-secret"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http script copy: "&redirect_uri="
2021/12/08 16:29:25 [debug] 21314#21314: *2 http script var: "https://rp.company.com:443"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http script var: "/_codexch"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http proxy header:
"POST /oxauth/restv1/token HTTP/1.0
Content-Type: application/x-www-form-urlencoded
Host: sso.company.com
Connection: close
Content-Length: 175
DNT: 1
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.45 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
sec-ch-ua: " Not A;Brand";v="99", "Chromium";v="96", "Google Chrome";v="96"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
Accept-Encoding: gzip, deflate, br
Accept-Language: en-SG,en;q=0.9
Cookie: auth_redir=/htvar/ugly.jsp; auth_nonce=85e6aa6169ae466048c5672482f4d03d

grant_type=authorization_code&client_id=my-client-id&code=b8bbd874-6eab-4282-b755-8501025ba68e&client_secret=my-client-secret&redirect_uri=https://rp.company.com:443/_codexch"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http cleanup add: 0000560F150445D0
2021/12/08 16:29:25 [debug] 21314#21314: *2 http finalize request: -4, "/_token?code=b8bbd874-6eab-4282-b755-8501025ba68e&client_secret=my-client-secret" a:0, c:3
2021/12/08 16:29:25 [debug] 21314#21314: *2 http request count:3 blk:0
2021/12/08 16:29:25 [debug] 21314#21314: *2 http run request: "/_codexch?code=b8bbd874-6eab-4282-b755-8501025ba68e&scope=user_role+user_type+openid+user_name+profile+email&session_id=31be09e0-b503-4928-9fa5-c2d706cdde4b&state=0&session_state=838d83a219f4e96aa1714452d1e6c5ff01c6cb12cfa69077f7e67b689f8c11bb.ac39bee8-bff6-4853-8d7c-18454f6f14f7"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http output filter "/_codexch?code=b8bbd874-6eab-4282-b755-8501025ba68e&scope=user_role+user_type+openid+user_name+profile+email&session_id=31be09e0-b503-4928-9fa5-c2d706cdde4b&state=0&session_state=838d83a219f4e96aa1714452d1e6c5ff01c6cb12cfa69077f7e67b689f8c11bb.ac39bee8-bff6-4853-8d7c-18454f6f14f7"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http copy filter: "/_codexch?code=b8bbd874-6eab-4282-b755-8501025ba68e&scope=user_role+user_type+openid+user_name+profile+email&session_id=31be09e0-b503-4928-9fa5-c2d706cdde4b&state=0&session_state=838d83a219f4e96aa1714452d1e6c5ff01c6cb12cfa69077f7e67b689f8c11bb.ac39bee8-bff6-4853-8d7c-18454f6f14f7"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http postpone filter "/_codexch?code=b8bbd874-6eab-4282-b755-8501025ba68e&scope=user_role+user_type+openid+user_name+profile+email&session_id=31be09e0-b503-4928-9fa5-c2d706cdde4b&state=0&session_state=838d83a219f4e96aa1714452d1e6c5ff01c6cb12cfa69077f7e67b689f8c11bb.ac39bee8-bff6-4853-8d7c-18454f6f14f7" 0000000000000000
2021/12/08 16:29:25 [debug] 21314#21314: *2 http copy filter: 0 "/_codexch?code=b8bbd874-6eab-4282-b755-8501025ba68e&scope=user_role+user_type+openid+user_name+profile+email&session_id=31be09e0-b503-4928-9fa5-c2d706cdde4b&state=0&session_state=838d83a219f4e96aa1714452d1e6c5ff01c6cb12cfa69077f7e67b689f8c11bb.ac39bee8-bff6-4853-8d7c-18454f6f14f7"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http upstream resolve: "/_token?code=b8bbd874-6eab-4282-b755-8501025ba68e&client_secret=my-client-secret"
2021/12/08 16:29:25 [debug] 21314#21314: *2 name was resolved to 192.168.0.168
2021/12/08 16:29:25 [debug] 21314#21314: *2 get rr peer, try: 1
2021/12/08 16:29:25 [debug] 21314#21314: *2 stream socket 18
2021/12/08 16:29:25 [debug] 21314#21314: *2 epoll add connection: fd:18 ev:80002005
2021/12/08 16:29:25 [debug] 21314#21314: *2 connect to 192.168.0.168:443, fd:18 #4
2021/12/08 16:29:25 [debug] 21314#21314: *2 http upstream connect: -2
2021/12/08 16:29:25 [debug] 21314#21314: *2 posix_memalign: 0000560F14FD03F0:128 @16
2021/12/08 16:29:25 [debug] 21314#21314: *2 event timer add: 18: 60000:429931761
2021/12/08 16:29:25 [debug] 21314#21314: *2 http upstream request: "/_token?code=b8bbd874-6eab-4282-b755-8501025ba68e&client_secret=my-client-secret"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http upstream send request handler
2021/12/08 16:29:25 [debug] 21314#21314: *2 malloc: 0000560F14F013A0:96
2021/12/08 16:29:25 [debug] 21314#21314: *2 upstream SSL server name: "sso.company.com"
2021/12/08 16:29:25 [debug] 21314#21314: *2 tcp_nodelay
2021/12/08 16:29:25 [debug] 21314#21314: *2 SSL_do_handshake: -1
2021/12/08 16:29:25 [debug] 21314#21314: *2 SSL_get_error: 2
2021/12/08 16:29:25 [debug] 21314#21314: *2 SSL handshake handler: 0
2021/12/08 16:29:25 [debug] 21314#21314: *2 SSL_do_handshake: -1
2021/12/08 16:29:25 [debug] 21314#21314: *2 SSL_get_error: 2
2021/12/08 16:29:25 [debug] 21314#21314: *2 SSL handshake handler: 1
2021/12/08 16:29:25 [debug] 21314#21314: *2 SSL_do_handshake: -1
2021/12/08 16:29:25 [debug] 21314#21314: *2 SSL_get_error: 2
2021/12/08 16:29:25 [debug] 21314#21314: *2 SSL handshake handler: 0
2021/12/08 16:29:25 [debug] 21314#21314: *2 SSL_do_handshake: 1
2021/12/08 16:29:25 [debug] 21314#21314: *2 SSL: TLSv1.2, cipher: "ECDHE-RSA-AES256-GCM-SHA384 TLSv1.2 Kx=ECDH Au=RSA Enc=AESGCM(256) Mac=AEAD"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http upstream ssl handshake: "/_token?code=b8bbd874-6eab-4282-b755-8501025ba68e&client_secret=my-client-secret"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http upstream send request
2021/12/08 16:29:25 [debug] 21314#21314: *2 http upstream send request body
2021/12/08 16:29:25 [debug] 21314#21314: *2 chain writer buf fl:1 s:1013
2021/12/08 16:29:25 [debug] 21314#21314: *2 chain writer in: 0000560F150448E8
2021/12/08 16:29:25 [debug] 21314#21314: *2 malloc: 0000560F1506AFF0:80
2021/12/08 16:29:25 [debug] 21314#21314: *2 malloc: 0000560F15056120:16384
2021/12/08 16:29:25 [debug] 21314#21314: *2 SSL buf copy: 1013
2021/12/08 16:29:25 [debug] 21314#21314: *2 SSL to write: 1013
2021/12/08 16:29:25 [debug] 21314#21314: *2 SSL_write: 1013
2021/12/08 16:29:25 [debug] 21314#21314: *2 chain writer out: 0000000000000000
2021/12/08 16:29:25 [debug] 21314#21314: *2 event timer del: 18: 429931761
2021/12/08 16:29:25 [debug] 21314#21314: *2 event timer add: 18: 60000:429931768
2021/12/08 16:29:25 [debug] 21314#21314: *2 http upstream process header
2021/12/08 16:29:25 [debug] 21314#21314: *2 malloc: 0000560F1505A130:4096
2021/12/08 16:29:25 [debug] 21314#21314: *2 SSL_read: -1
2021/12/08 16:29:25 [debug] 21314#21314: *2 SSL_get_error: 2
2021/12/08 16:29:25 [debug] 21314#21314: *2 http upstream request: "/_token?code=b8bbd874-6eab-4282-b755-8501025ba68e&client_secret=my-client-secret"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http upstream dummy handler
2021/12/08 16:29:25 [debug] 21314#21314: *2 http upstream request: "/_token?code=b8bbd874-6eab-4282-b755-8501025ba68e&client_secret=my-client-secret"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http upstream process header
2021/12/08 16:29:25 [debug] 21314#21314: *2 SSL_read: 343
2021/12/08 16:29:25 [debug] 21314#21314: *2 SSL_read: 628
2021/12/08 16:29:25 [debug] 21314#21314: *2 SSL_read: 0
2021/12/08 16:29:25 [debug] 21314#21314: *2 SSL_get_error: 5
2021/12/08 16:29:25 [debug] 21314#21314: *2 peer shutdown SSL cleanly
2021/12/08 16:29:25 [debug] 21314#21314: *2 http proxy status 401 "401 Unauthorized"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http proxy header: "Date: Wed, 08 Dec 2021 08:29:25 GMT"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http proxy header: "Server: Apache"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http proxy header: "X-Xss-Protection: 1; mode=block"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http proxy header: "X-Content-Type-Options: nosniff"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http proxy header: "Strict-Transport-Security: max-age=31536000; includeSubDomains"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http proxy header: "WWW-Authenticate: Basic realm="oxAuth""
2021/12/08 16:29:25 [debug] 21314#21314: *2 http proxy header: "Content-Type: application/json;charset=iso-8859-1"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http proxy header: "Content-Length: 628"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http proxy header: "Connection: close"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http proxy header done
2021/12/08 16:29:25 [debug] 21314#21314: *2 http cacheable: 0
2021/12/08 16:29:25 [debug] 21314#21314: *2 http proxy filter init s:401 h:0 c:0 l:628
2021/12/08 16:29:25 [debug] 21314#21314: *2 http upstream process upstream
2021/12/08 16:29:25 [debug] 21314#21314: *2 pipe read upstream: 1
2021/12/08 16:29:25 [debug] 21314#21314: *2 pipe preread: 628
2021/12/08 16:29:25 [debug] 21314#21314: *2 pipe recv chain: 0
2021/12/08 16:29:25 [debug] 21314#21314: *2 pipe buf free s:0 t:1 f:0 0000560F1505A130, pos 0000560F1505A287, size: 628 file: 0, size: 0
2021/12/08 16:29:25 [debug] 21314#21314: *2 pipe length: 628
2021/12/08 16:29:25 [debug] 21314#21314: *2 input buf #0
2021/12/08 16:29:25 [debug] 21314#21314: *2 pipe write downstream: 1
2021/12/08 16:29:25 [debug] 21314#21314: *2 pipe write downstream flush in
2021/12/08 16:29:25 [debug] 21314#21314: *2 http output filter "/_token?code=b8bbd874-6eab-4282-b755-8501025ba68e&client_secret=my-client-secret"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http copy filter: "/_token?code=b8bbd874-6eab-4282-b755-8501025ba68e&client_secret=my-client-secret"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http postpone filter "/_token?code=b8bbd874-6eab-4282-b755-8501025ba68e&client_secret=my-client-secret" 0000560F15055BE8
2021/12/08 16:29:25 [debug] 21314#21314: *2 http postpone filter in memory
2021/12/08 16:29:25 [debug] 21314#21314: *2 http postpone filter in memory 628 bytes
2021/12/08 16:29:25 [debug] 21314#21314: *2 http copy filter: 0 "/_token?code=b8bbd874-6eab-4282-b755-8501025ba68e&client_secret=my-client-secret"
2021/12/08 16:29:25 [debug] 21314#21314: *2 pipe write downstream done
2021/12/08 16:29:25 [debug] 21314#21314: *2 event timer: 18, old: 429931768, new: 429931781
2021/12/08 16:29:25 [debug] 21314#21314: *2 http upstream exit: 0000000000000000
2021/12/08 16:29:25 [debug] 21314#21314: *2 finalize http upstream request: 0
2021/12/08 16:29:25 [debug] 21314#21314: *2 finalize http proxy request
2021/12/08 16:29:25 [debug] 21314#21314: *2 free rr peer 1 0
2021/12/08 16:29:25 [debug] 21314#21314: *2 SSL_shutdown: 1
2021/12/08 16:29:25 [debug] 21314#21314: *2 close http upstream connection: 18
2021/12/08 16:29:25 [debug] 21314#21314: *2 free: 0000560F15056120
2021/12/08 16:29:25 [debug] 21314#21314: *2 free: 0000560F1506AFF0
2021/12/08 16:29:25 [debug] 21314#21314: *2 free: 0000560F14F013A0
2021/12/08 16:29:25 [debug] 21314#21314: *2 free: 0000560F14FD03F0, unused: 0
2021/12/08 16:29:25 [debug] 21314#21314: *2 event timer del: 18: 429931768
2021/12/08 16:29:25 [debug] 21314#21314: *2 reusable connection: 0
2021/12/08 16:29:25 [debug] 21314#21314: *2 http upstream temp fd: -1
2021/12/08 16:29:25 [debug] 21314#21314: *2 http output filter "/_token?code=b8bbd874-6eab-4282-b755-8501025ba68e&client_secret=my-client-secret"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http copy filter: "/_token?code=b8bbd874-6eab-4282-b755-8501025ba68e&client_secret=my-client-secret"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http postpone filter "/_token?code=b8bbd874-6eab-4282-b755-8501025ba68e&client_secret=my-client-secret" 00007FFE9BFA0D60
2021/12/08 16:29:25 [debug] 21314#21314: *2 http postpone filter in memory
2021/12/08 16:29:25 [debug] 21314#21314: *2 http copy filter: 0 "/_token?code=b8bbd874-6eab-4282-b755-8501025ba68e&client_secret=my-client-secret"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http finalize request: 0, "/_token?code=b8bbd874-6eab-4282-b755-8501025ba68e&client_secret=my-client-secret" a:0, c:2
2021/12/08 16:29:25 [debug] 21314#21314: *2 posix_memalign: 0000560F15056120:4096 @16
2021/12/08 16:29:25 [error] 21314#21314: *2 js: OIDC error from IdP when sending authorization code: invalid_client, Client authentication failed (e.g. unknown client, no client authentication included, or unsupported authentication method). The authorization server MAY return an HTTP 401 (Unauthorized) status code to indicate which HTTP authentication schemes are supported. If the client attempted to authenticate via the Authorization request header field, the authorization server MUST respond with an HTTP 401 (Unauthorized) status code, and include the WWW-Authenticate response header field matching the authentication scheme used by the client.
2021/12/08 16:29:25 [debug] 21314#21314: *2 http request count:2 blk:0
2021/12/08 16:29:25 [debug] 21314#21314: *2 http posted request: "/_codexch?code=b8bbd874-6eab-4282-b755-8501025ba68e&scope=user_role+user_type+openid+user_name+profile+email&session_id=31be09e0-b503-4928-9fa5-c2d706cdde4b&state=0&session_state=838d83a219f4e96aa1714452d1e6c5ff01c6cb12cfa69077f7e67b689f8c11bb.ac39bee8-bff6-4853-8d7c-18454f6f14f7"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http finalize request: 502, "/_codexch?code=b8bbd874-6eab-4282-b755-8501025ba68e&scope=user_role+user_type+openid+user_name+profile+email&session_id=31be09e0-b503-4928-9fa5-c2d706cdde4b&state=0&session_state=838d83a219f4e96aa1714452d1e6c5ff01c6cb12cfa69077f7e67b689f8c11bb.ac39bee8-bff6-4853-8d7c-18454f6f14f7" a:1, c:1
2021/12/08 16:29:25 [debug] 21314#21314: *2 http special response: 502, "/_codexch?code=b8bbd874-6eab-4282-b755-8501025ba68e&scope=user_role+user_type+openid+user_name+profile+email&session_id=31be09e0-b503-4928-9fa5-c2d706cdde4b&state=0&session_state=838d83a219f4e96aa1714452d1e6c5ff01c6cb12cfa69077f7e67b689f8c11bb.ac39bee8-bff6-4853-8d7c-18454f6f14f7"
2021/12/08 16:29:25 [debug] 21314#21314: *2 test location: "@do_oidc_flow"
2021/12/08 16:29:25 [debug] 21314#21314: *2 test location: "@oidc_error"
2021/12/08 16:29:25 [debug] 21314#21314: *2 using location: @oidc_error "/_codexch?code=b8bbd874-6eab-4282-b755-8501025ba68e&scope=user_role+user_type+openid+user_name+profile+email&session_id=31be09e0-b503-4928-9fa5-c2d706cdde4b&state=0&session_state=838d83a219f4e96aa1714452d1e6c5ff01c6cb12cfa69077f7e67b689f8c11bb.ac39bee8-bff6-4853-8d7c-18454f6f14f7"
2021/12/08 16:29:25 [debug] 21314#21314: *2 rewrite phase: 3
2021/12/08 16:29:25 [debug] 21314#21314: *2 http script var: "NGINX / OpenID Connect login failure
"
2021/12/08 16:29:25 [debug] 21314#21314: *2 HTTP/1.1 502 Bad Gateway
Server: nginx/1.21.3
Date: Wed, 08 Dec 2021 08:29:25 GMT
Content-Type: text/plain
Content-Length: 37
Connection: keep-alive

2021/12/08 16:29:25 [debug] 21314#21314: *2 write new buf t:1 f:0 0000560F15056520, pos 0000560F15056520, size: 157 file: 0, size: 0
2021/12/08 16:29:25 [debug] 21314#21314: *2 http write filter: l:0 f:0 s:157
2021/12/08 16:29:25 [debug] 21314#21314: *2 http output filter "/_codexch?code=b8bbd874-6eab-4282-b755-8501025ba68e&scope=user_role+user_type+openid+user_name+profile+email&session_id=31be09e0-b503-4928-9fa5-c2d706cdde4b&state=0&session_state=838d83a219f4e96aa1714452d1e6c5ff01c6cb12cfa69077f7e67b689f8c11bb.ac39bee8-bff6-4853-8d7c-18454f6f14f7"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http copy filter: "/_codexch?code=b8bbd874-6eab-4282-b755-8501025ba68e&scope=user_role+user_type+openid+user_name+profile+email&session_id=31be09e0-b503-4928-9fa5-c2d706cdde4b&state=0&session_state=838d83a219f4e96aa1714452d1e6c5ff01c6cb12cfa69077f7e67b689f8c11bb.ac39bee8-bff6-4853-8d7c-18454f6f14f7"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http postpone filter "/_codexch?code=b8bbd874-6eab-4282-b755-8501025ba68e&scope=user_role+user_type+openid+user_name+profile+email&session_id=31be09e0-b503-4928-9fa5-c2d706cdde4b&state=0&session_state=838d83a219f4e96aa1714452d1e6c5ff01c6cb12cfa69077f7e67b689f8c11bb.ac39bee8-bff6-4853-8d7c-18454f6f14f7" 00007FFE9BFA0C50
2021/12/08 16:29:25 [debug] 21314#21314: *2 write old buf t:1 f:0 0000560F15056520, pos 0000560F15056520, size: 157 file: 0, size: 0
2021/12/08 16:29:25 [debug] 21314#21314: *2 write new buf t:0 f:0 0000000000000000, pos 0000560F15056458, size: 37 file: 0, size: 0
2021/12/08 16:29:25 [debug] 21314#21314: *2 http write filter: l:1 f:0 s:194
2021/12/08 16:29:25 [debug] 21314#21314: *2 http write filter limit 0
2021/12/08 16:29:25 [debug] 21314#21314: *2 malloc: 0000560F15067830:16384
2021/12/08 16:29:25 [debug] 21314#21314: *2 SSL buf copy: 157
2021/12/08 16:29:25 [debug] 21314#21314: *2 SSL buf copy: 37
2021/12/08 16:29:25 [debug] 21314#21314: *2 SSL to write: 194
2021/12/08 16:29:25 [debug] 21314#21314: *2 SSL_write: 194
2021/12/08 16:29:25 [debug] 21314#21314: *2 http write filter 0000000000000000
2021/12/08 16:29:25 [debug] 21314#21314: *2 http copy filter: 0 "/_codexch?code=b8bbd874-6eab-4282-b755-8501025ba68e&scope=user_role+user_type+openid+user_name+profile+email&session_id=31be09e0-b503-4928-9fa5-c2d706cdde4b&state=0&session_state=838d83a219f4e96aa1714452d1e6c5ff01c6cb12cfa69077f7e67b689f8c11bb.ac39bee8-bff6-4853-8d7c-18454f6f14f7"
2021/12/08 16:29:25 [debug] 21314#21314: *2 http finalize request: 0, "/_codexch?code=b8bbd874-6eab-4282-b755-8501025ba68e&scope=user_role+user_type+openid+user_name+profile+email&session_id=31be09e0-b503-4928-9fa5-c2d706cdde4b&state=0&session_state=838d83a219f4e96aa1714452d1e6c5ff01c6cb12cfa69077f7e67b689f8c11bb.ac39bee8-bff6-4853-8d7c-18454f6f14f7" a:1, c:2
2021/12/08 16:29:25 [debug] 21314#21314: *2 http request count:2 blk:0
2021/12/08 16:29:25 [debug] 21314#21314: *2 http finalize request: -4, "/_codexch?code=b8bbd874-6eab-4282-b755-8501025ba68e&scope=user_role+user_type+openid+user_name+profile+email&session_id=31be09e0-b503-4928-9fa5-c2d706cdde4b&state=0&session_state=838d83a219f4e96aa1714452d1e6c5ff01c6cb12cfa69077f7e67b689f8c11bb.ac39bee8-bff6-4853-8d7c-18454f6f14f7" a:1, c:1
2021/12/08 16:29:25 [debug] 21314#21314: *2 set http keepalive handler
2021/12/08 16:29:25 [debug] 21314#21314: *2 http close request
2021/12/08 16:29:25 [debug] 21314#21314: *2 http log handler
2021/12/08 16:29:25 [debug] 21314#21314: *2 http session log handler
2021/12/08 16:29:25 [debug] 21314#21314: *2 run cleanup: 0000560F1504E8D8
2021/12/08 16:29:25 [debug] 21314#21314: *2 free: 0000560F1505A130
2021/12/08 16:29:25 [debug] 21314#21314: *2 free: 0000560F14F12A60, unused: 3
2021/12/08 16:29:25 [debug] 21314#21314: *2 free: 0000560F1504E2C0, unused: 4
2021/12/08 16:29:25 [debug] 21314#21314: *2 free: 0000560F15043910, unused: 8
2021/12/08 16:29:25 [debug] 21314#21314: *2 free: 0000560F15055110, unused: 0
2021/12/08 16:29:25 [debug] 21314#21314: *2 free: 0000560F15056120, unused: 2189
2021/12/08 16:29:25 [debug] 21314#21314: *2 free: 0000560F15052B80
2021/12/08 16:29:25 [debug] 21314#21314: *2 hc free: 0000000000000000
2021/12/08 16:29:25 [debug] 21314#21314: *2 hc busy: 0000560F15052740 1
2021/12/08 16:29:25 [debug] 21314#21314: *2 free: 0000560F1504F2D0
2021/12/08 16:29:25 [debug] 21314#21314: *2 free: 0000560F15067830
2021/12/08 16:29:25 [debug] 21314#21314: *2 reusable connection: 1
2021/12/08 16:29:25 [debug] 21314#21314: *2 event timer add: 16: 65000:429936781

Nginx Version

# nginx -V
nginx version: nginx/1.21.3 (nginx-plus-r25)
built by gcc 4.8.5 20150623 (Red Hat 4.8.5-44) (GCC)
built with OpenSSL 1.0.2k-fips  26 Jan 2017
TLS SNI support enabled
configure arguments: --prefix=/etc/nginx --sbin-path=/usr/sbin/nginx --modules-path=/usr/lib64/nginx/modules --conf-path=/etc/nginx/nginx.conf --error-log-path=/var/log/nginx/error.log --http-log-path=/var/log/nginx/access.log --pid-path=/var/run/nginx.pid --lock-path=/var/run/nginx.lock --http-client-body-temp-path=/var/cache/nginx/client_temp --http-proxy-temp-path=/var/cache/nginx/proxy_temp --http-fastcgi-temp-path=/var/cache/nginx/fastcgi_temp --http-uwsgi-temp-path=/var/cache/nginx/uwsgi_temp --http-scgi-temp-path=/var/cache/nginx/scgi_temp --user=nginx --group=nginx --with-compat --with-file-aio --with-threads --with-http_addition_module --with-http_auth_request_module --with-http_dav_module --with-http_flv_module --with-http_gunzip_module --with-http_gzip_static_module --with-http_mp4_module --with-http_random_index_module --with-http_realip_module --with-http_secure_link_module --with-http_slice_module --with-http_ssl_module --with-http_stub_status_module --with-http_sub_module --with-http_v2_module --with-mail --with-mail_ssl_module --with-stream --with-stream_realip_module --with-stream_ssl_module --with-stream_ssl_preread_module --build=nginx-plus-r25 --with-http_auth_jwt_module --with-http_f4f_module --with-http_hls_module --with-http_session_log_module --with-cc-opt='-O2 -g -pipe -Wall -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -fstack-protector-strong --param=ssp-buffer-size=4 -grecord-gcc-switches -m64 -mtune=generic -DIP_BIND_ADDRESS_NO_PORT=24 -fPIC' --with-ld-opt='-Wl,-z,relro -Wl,-z,now -pie'

Any idea if I missed out on any any configuration for URI?

nginx-plus-module-njs is not available for Amazon Linux

I'm trying to play the OIDC tutorial here, and getting a trouble to install nginx-plus-module-njs on my Amazon Linux instance. Here comes the detail.

$ uname -r
4.14.214-160.339.amzn2.x86_64
$ docker pull nginx:latest
$ docker run --name my-nginx -d -p 8080:80 nginx:latest
$ sudo yum install nginx-plus-module-njs
読み込んだプラグイン:extras_suggestions, langpacks, priorities, update-motd
amzn2-core | 3.7 kB 00:00:00
パッケージ nginx-plus-module-njs は利用できません。
エラー: 何もしません

The Japanese log tells "it's unavailable, and nothing has been done."

Any workarounds?

Support for Iframe embed and third party cookie blocking.

Most browsers are aggressively blocking cookies when page belonging to different TLD is loaded in an iframe. This also impacts OIDC based integrations where application page is embedded in other portals inside iframe. The current implementation of nginx relies on opaque token sent as cookie.

Are there any plans to provide an alternate option. Some of possible things could be:

  1. Fallback to local/session storage if cookie support is not detected.
  2. Provide a parameter for opening the IDP page in a popup so that the cookies created by IDP are treated as First Party.

Token Request Through Corporate Proxy

How do we change the token request to go through a corporate proxy server.

location = /token {
# This location is called by oidcCodeExchange(). We use the proxy
directives
# to construct the OpenID Connect token request, as per:
# http://openid.net/specs/openid-connect-core-1_0.html#TokenRequest
internal;
proxy_ssl_server_name on; # For SNI to the IdP
proxy_set_header Content-Type "application/x-www-form-urlencoded";
proxy_set_body "grant_type=authorization_code&code=$arg_code&client_id=$oidc_client&client_secret=$oidc_client_secret&redirect_uri=$scheme://$host:$server_port$redir_location";
proxy_method POST;
proxy_pass $oidc_token_endpoint;
}

Add access token support

As a Product Manager,

I want to use access token for the API authorization in the apps as the current NJS implementation doesn't support access token yet.

AC:

  • Capture access_token in the NJS code.
  • Store access_token in the key/value store as same as the NGINX RP stores id_token and refresh_token.

Session stickiness required in multi node HA deployment

The OIDC plugin requires session stickiness to be enabled in case of highly available deployment topology. This is an anti pattern. Can something be done for removing this dependency?

Browsers typically send request for multiple resources for a site in parallel. In a multi node deployment with round robin load balancing algorithm, the login process can potentially get triggered for multiple requests.

Questions about [NginxPlus + nginx-openid-connect] proxy use on the intranet

Hello

Our company is intended [NginxPlus + nginx-openid-connect] to be used in the intranet

The following problem arises

图片

errorlog:
「503 login.microsoftonline.com could not be resolved」
「js: OIDC unexpected response from IdP when sending authorization code (HTTP 502). 」

Question:

1. Is there a setting like "OIDCOutgoingProxy" in NginxPlus or in nginx-openid-connect module? (Apache+OIDC has such a configuration parameter)

2. Why NginxPlus cannot analyze "login.microsoftonline.com"? Error log: "503 login.microsoftonline.com could not be resolved".

thanks a lot

Add certificate‑bound access tokens support to this OIDC Reference Implementation

Could mutual TLS (mTLS) client certificate‑bound access tokens (defined in RFC 8705) integration be added into NGINX's OIDC Reference implementation. An example of its implementation was provided in a recent NGINX Plus R25 Blog Post. The OIDC Reference implementation would just need to validate that the cnf claim and client cert exists before performing the validation or provide an option to enable the feature. Please let me know if anymore detail would be helpful.

Reset the cookie value after successful token exchange

After successful token exchange, when you try to access multiple apis which is part of server has the reference to first api that was set for redirect - auth_redir=

Reset the value of this session cookie after the successful token exchange

error log printing variable rather than it's value

If audience validation fails it throws error & dumps it to oidc_error.log as expected. But in error message instead of showing "$oidc_client" value it prints it as such.

openid_connect.js: here if you see the variable is inside the static print value instead of being outside it.
req.error("OIDC ID Token validation error: aud claim (" + req.vari ables.jwt_claim_aud + ") does not match $oidc_client");

It's a cosmetic bug. I have verified this on google cloud nginx plus vm instance (provided by its marketplace).

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.