Skip to content

Add OIDC end session endpoint and custom query params #72

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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

Expand All @@ -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.

Expand Down
4 changes: 2 additions & 2 deletions configure.sh
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ fi
# Build an intermediate configuration file
# File format is: <NGINX variable name><space><IdP value>
#
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
Expand Down Expand Up @@ -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 ..."
Expand Down
18 changes: 16 additions & 2 deletions openid_connect.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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) {
Expand Down
19 changes: 16 additions & 3 deletions openid_connect.server_conf
Original file line number Diff line number Diff line change
Expand Up @@ -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";
}
Expand Down
20 changes: 16 additions & 4 deletions openid_connect_configuration.conf
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this approach can still confuse the inexperienced user:

  1. We cannot replace the post_logout_redirect_uri parameter with something of our own, because the _logout location will perform additional functions.
  2. We already have 2 similar parameters: post_logout_redirect_uri and $oidc_logout_landing_page.

Perhaps it makes sense to hide post_logout_redirect_uri=$redirect_base/_logout&.

Copy link
Contributor Author

@shawnhankim shawnhankim Jan 5, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Background:

  • The RP-Initiated Logout of OIDC spec uses post_logout_redirect_uri for most of IdPs.

  • However, all of fields are not required, and several IdPs use different variable name as the following example.

    • Example 1. Default RP-Initiated Logout Parameter

      {
          "post_logout_redirect_uri": "$redirect_base/_logout",
          "id_token_hint": "$session_jwt"
      }
    • Example 2. AWS Cognito Logout Parameter
      If the following parameters are just added as extra arguments, then the unnecessary default parameters are sent to the IdP, and post_logout_redirect_uri & logout_uri values are also redundant.

      {
           "client_id"  : "$oidc_client",
           "logout_uri" : "$redirect_base/_logout"
      }
    • Example 3. AWS Cognito Logout Parameter to prompt a user to sign in as another user.
      If the following parameters are just added as extra arguments, then the unnecessary default parameters are sent to the IdP, and post_logout_redirect_uri & redirect_uri values are also redundant.

      {
          "response_type": "code",
          "client_id"    : "$oidc_client",
          "redirect_uri" : "$redirect_base$redir_location",
          "state"        : "STATE",
          "scope"        : "$oidc_scopes"
      }
    • Example 4. Auth0 Logout Parameter
      If the following parameters are just added as extra arguments, then the unnecessary default parameters are sent to the IdP, and post_logout_redirect_uri & returnTo values are also redundant.

      {
          "client_id": "$oidc_client",
          "returnTo" : "$redirect_base/_logout"
      }

Summary:
In the file of openid_connect.server_conf:

  • $oidc_logout_callback_param had been added so the key/value are hidden as you suggested.
  • $oidc_logout_callback_uri has been added so customers can reuse it as a value of different key of query param per each IdP.

#www.example.com "client_id=$oidc_client&logout_uri=$redirect_base/_logout";
}

map $host $oidc_client {
default "my-client-id";
}
Expand All @@ -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 {
Expand Down