From fc05d36d4f8bf6b8bd842f5aed65d24dcab91c63 Mon Sep 17 00:00:00 2001 From: Shawn Kim Date: Wed, 21 Dec 2022 18:00:00 -0800 Subject: [PATCH] Add OIDC end session endpoint support rename oidc_end_session_query_params fix: typo --- README.md | 5 +++-- configure.sh | 4 ++-- openid_connect.js | 18 ++++++++++++++++-- openid_connect.server_conf | 19 ++++++++++++++++--- openid_connect_configuration.conf | 20 ++++++++++++++++---- 5 files changed, 53 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 969dcd7..65b2692 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ If a [refresh token](https://openid.net/specs/openid-connect-core-1_0.html#Refre ### 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. +Requests made to the `/logout` location invalidate both the ID token and refresh token by erasing them from the key-value store. Next, the User Agent is redirected to the `$oidc_end_session_endpoint` in order to terminate the user session on the IdP's side. After the session is successfully terminated on the IdP side, the User Agent will be redirected to the `$oidc_logout_landing_page`. Note that the `$oidc_logout_landing_page` endpoint must not require authentication, otherwise the user authentication process may be initiated from the beginning. ### Multiple IdPs @@ -102,6 +102,7 @@ When NGINX Plus is deployed behind another proxy, the original protocol and port * 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** + * Obtain the URL for the **end session endpoint** ## Configuring NGINX Plus @@ -111,7 +112,7 @@ Manual configuration involves reviewing the following files so that they match y * **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 + * Modify the URI defined in `map…$oidc_logout_landing_page` to redirect browser after successful logout * 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. diff --git a/configure.sh b/configure.sh index 17e8920..826addd 100755 --- a/configure.sh +++ b/configure.sh @@ -120,7 +120,7 @@ fi # Build an intermediate configuration file # File format is: # -jq -r '. | "$oidc_authz_endpoint \(.authorization_endpoint)\n$oidc_token_endpoint \(.token_endpoint)\n$oidc_jwks_uri \(.jwks_uri)"' < /tmp/${COMMAND}_$$_json > /tmp/${COMMAND}_$$_conf +jq -r '. | "$oidc_authz_endpoint \(.authorization_endpoint)\n$oidc_token_endpoint \(.token_endpoint)\n$oidc_jwks_uri \(.jwks_uri)\n$oidc_end_session_endpoint \(.end_session_endpoint)"' < /tmp/${COMMAND}_$$_json > /tmp/${COMMAND}_$$_conf # Create a random value for HMAC key, adding to the intermediate configuration file echo "\$oidc_hmac_key `openssl rand -base64 18`" >> /tmp/${COMMAND}_$$_conf @@ -178,7 +178,7 @@ fi # Loop through each configuration variable echo "$COMMAND: NOTICE: Configuring $CONFDIR/openid_connect_configuration.conf" -for OIDC_VAR in \$oidc_authz_endpoint \$oidc_token_endpoint \$oidc_jwt_keyfile \$oidc_hmac_key $CLIENT_ID_VAR $CLIENT_SECRET_VAR $PKCE_ENABLE_VAR; do +for OIDC_VAR in \$oidc_authz_endpoint \$oidc_token_endpoint \$oidc_jwt_keyfile \$oidc_end_session_endpoint \$oidc_hmac_key $CLIENT_ID_VAR $CLIENT_SECRET_VAR $PKCE_ENABLE_VAR; do # Pull the configuration value from the intermediate file VALUE=`grep "^$OIDC_VAR " /tmp/${COMMAND}_$$_conf | cut -f2 -d' '` echo -n "$COMMAND: NOTICE: - $OIDC_VAR ..." diff --git a/openid_connect.js b/openid_connect.js index 49909c9..1906e1b 100644 --- a/openid_connect.js +++ b/openid_connect.js @@ -5,7 +5,7 @@ */ var newSession = false; // Used by oidcAuth() and validateIdToken() -export default {auth, codeExchange, validateIdToken, logout}; +export default {auth, codeExchange, validateIdToken, logout, redirectPostLogout}; function retryOriginalRequest(r) { delete r.headersOut["WWW-Authenticate"]; // Remove evidence of original failed auth_jwt @@ -263,12 +263,26 @@ function validateIdToken(r) { } } +// Default RP-Initiated or Custom Logout w/ OP as per: +// https://openid.net/specs/openid-connect-rpinitiated-1_0.html#RPLogout +// https://openid.net/specs/openid-connect-rpinitiated-1_0.html#RedirectionAfterLogout +// An RP requests that the OP log out the end-user by redirecting the end-user's +// User Agent to the OP's Logout endpoint. function logout(r) { r.log("OIDC logout for " + r.variables.cookie_auth_token); + var queryParams = ''; + if (r.variables.oidc_end_session_query_params) { + queryParams = '?' + r.variables.oidc_end_session_query_params; + } r.variables.session_jwt = "-"; r.variables.access_token = "-"; r.variables.refresh_token = "-"; - r.return(302, r.variables.oidc_logout_redirect); + r.return(302, r.variables.oidc_end_session_endpoint + queryParams); +} + +// Redirect URI after logged-out from the OP. +function redirectPostLogout(r) { + r.return(302, r.variables.oidc_logout_landing_page); } function getAuthZArgs(r) { diff --git a/openid_connect.server_conf b/openid_connect.server_conf index 13456d2..aa47f3a 100644 --- a/openid_connect.server_conf +++ b/openid_connect.server_conf @@ -67,14 +67,27 @@ } location = /logout { + # RP-Initiated Logout to interact with $oidc_end_session_endpoint as per: + # https://openid.net/specs/openid-connect-rpinitiated-1_0.html#RPLogout 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) + # This location is a RP's callback URI which is called by the IdP after + # successful logout from the IdP by calling $oidc_logout_endpoint. + + # Clean cookies + add_header Set-Cookie "auth_token=; $oidc_cookie_flags"; # Send empty cookie + add_header Set-Cookie "auth_redir=; $oidc_cookie_flags"; # Erase original cookie + add_header Set-Cookie "auth_nonce=; $oidc_cookie_flags"; + + js_content oidc.redirectPostLogout; + } + + location = /logout_page { + # This location is a default value of $oidc_logout_landing_page as a + # Built-in, simple logout page in case it wasn't configured. default_type text/plain; return 200 "Logged out\n"; } diff --git a/openid_connect_configuration.conf b/openid_connect_configuration.conf index 0aa69a4..6cb7084 100644 --- a/openid_connect_configuration.conf +++ b/openid_connect_configuration.conf @@ -28,6 +28,18 @@ map $host $oidc_jwt_keyfile { default "http://127.0.0.1:8080/auth/realms/master/protocol/openid-connect/certs"; } +map $host $oidc_end_session_endpoint { + default "http://127.0.0.1:8080/auth/realms/master/protocol/openid-connect/logout"; +} + +map $host $oidc_end_session_query_params { + # Each IdP may use different query params of the $oidc_end_session_endpoint. + # For example, Amazon Cognito requires 'logout_uri', and Auth0 requires + # 'returnTo' instead of 'post_logout_redirect_uri' in the query params. + default "post_logout_redirect_uri=$redirect_base/_logout&id_token_hint=$session_jwt"; + #www.example.com "client_id=$oidc_client&logout_uri=$redirect_base/_logout"; +} + map $host $oidc_client { default "my-client-id"; } @@ -44,10 +56,10 @@ 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_logout_landing_page { + # Where to redirect browser after successful logout from the IdP. + default "$redirect_base/logout_page"; # Built-in, simple logout page + #www.example.com $redirect_base; } map $host $oidc_hmac_key {