Skip to content

Commit 323e6f9

Browse files
committed
Add support for RP-initiated OIDC logout
Implement support for RP-initiated logout in accordance with OpenID Connect RP-Initiated Logout 1.0. Introduce "oidc_end_session_endpoint" variable to specify the "end_session_endpoint" URL. If "oidc_end_session_endpoint" is not set or is empty, the default behavior of logging out only on the NGINX side is maintained. When set, the endpoint triggers the RP-initiated logout as specified in the specification.
1 parent 8dad580 commit 323e6f9

File tree

5 files changed

+68
-16
lines changed

5 files changed

+68
-16
lines changed

README.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,10 @@ If a [refresh token](https://openid.net/specs/openid-connect-core-1_0.html#Refre
8484

8585
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.
8686

87+
#### RP-Initiated OIDC Logout
88+
89+
RP-initiated logout is supported according to [OpenID Connect RP-Initiated Logout 1.0](https://openid.net/specs/openid-connect-rpinitiated-1_0.html). This behavior is controlled by the `$oidc_end_session_endpoint` variable.
90+
8791
### Multiple IdPs
8892

8993
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.
@@ -137,11 +141,13 @@ When NGINX Plus is deployed behind another proxy, the original protocol and port
137141
* 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`
138142
* Ensure NGINX Plus is configured as a confidential client (with a client secret) or a public client (with PKCE S256 enabled)
139143
* Make a note of the `client ID` and `client secret` if set
144+
* Set the **post logout redirect URI** to the address of your NGINX Plus instance (including the port number), with `/_logout` as the path, e.g. `https://my-nginx.example.com:443/_logout`
140145

141146
* 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:
142147
* Obtain the URL for `jwks_uri` or download the JWK file to your NGINX Plus instance
143148
* Obtain the URL for the **authorization endpoint**
144149
* Obtain the URL for the **token endpoint**
150+
* Obtain the URL for the **end session endpoint**
145151

146152
## Configuring NGINX Plus
147153

@@ -165,7 +171,7 @@ Manual configuration involves reviewing the following files so that they match y
165171

166172
* **openid_connect.server_conf** - this is the NGINX configuration for handling the various stages of OpenID Connect authorization code flow
167173
* No changes are usually required here
168-
* Modify the `resolver` directive to match a DNS server that is capable of resolving the IdP defined in `$oidc_token_endpoint`
174+
* Modify the `resolver` directive to match a DNS server that is capable of resolving the IdP defined in `$oidc_token_endpoint` and `$oidc_end_session_endpoint`
169175
* If using [`auth_jwt_key_request`](http://nginx.org/en/docs/http/ngx_http_auth_jwt_module.html#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
170176

171177
* **openid_connect.js** - this is the JavaScript code for performing the authorization code exchange and nonce hashing

configure.sh

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ fi
120120
# Build an intermediate configuration file
121121
# File format is: <NGINX variable name><space><IdP value>
122122
#
123-
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
123+
jq -r '. | "$oidc_authz_endpoint \(.authorization_endpoint)\n$oidc_token_endpoint \(.token_endpoint)\n$oidc_end_session_endpoint \(.end_session_endpoint // "")\n$oidc_jwks_uri \(.jwks_uri)"' < /tmp/${COMMAND}_$$_json > /tmp/${COMMAND}_$$_conf
124124

125125
# Create a random value for HMAC key, adding to the intermediate configuration file
126126
echo "\$oidc_hmac_key `openssl rand -base64 18`" >> /tmp/${COMMAND}_$$_conf
@@ -178,13 +178,18 @@ fi
178178

179179
# Loop through each configuration variable
180180
echo "$COMMAND: NOTICE: Configuring $CONFDIR/openid_connect_configuration.conf"
181-
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
181+
for OIDC_VAR in \$oidc_authz_endpoint \$oidc_token_endpoint \$oidc_end_session_endpoint \$oidc_jwt_keyfile \$oidc_hmac_key $CLIENT_ID_VAR $CLIENT_SECRET_VAR $PKCE_ENABLE_VAR; do
182182
# Pull the configuration value from the intermediate file
183183
VALUE=`grep "^$OIDC_VAR " /tmp/${COMMAND}_$$_conf | cut -f2 -d' '`
184184
echo -n "$COMMAND: NOTICE: - $OIDC_VAR ..."
185185

186+
# If the value is empty, assign a default value
187+
if [ -z "$VALUE" ]; then
188+
VALUE="\"\""
189+
fi
190+
186191
# Find where this variable is configured
187-
LINE=`grep -nA10 $OIDC_VAR $CONFDIR/openid_connect_configuration.conf | grep $HOSTNAME | head -1 | cut -f1 -d-`
192+
LINE=`grep -nA10 $OIDC_VAR $CONFDIR/openid_connect_configuration.conf | grep -vE '^[0-9]+-?[[:space:]]*($|#)' | grep $HOSTNAME | head -1 | cut -f1 -d-`
188193
if [ "$LINE" == "" ]; then
189194
# Add new value
190195
LINE=`grep -n $OIDC_VAR $CONFDIR/openid_connect_configuration.conf | head -1 | cut -f1 -d:`

openid_connect.js

Lines changed: 40 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/*
22
* JavaScript functions for providing OpenID Connect with NGINX Plus
3-
*
3+
*
44
* Copyright (C) 2020 Nginx, Inc.
55
*/
66
var newSession = false; // Used by oidcAuth() and validateIdToken()
@@ -51,7 +51,7 @@ function auth(r, afterSyncCheck) {
5151
r.return(302, r.variables.oidc_authz_endpoint + getAuthZArgs(r));
5252
return;
5353
}
54-
54+
5555
// Pass the refresh token to the /_refresh location so that it can be
5656
// proxied to the IdP in exchange for a new id_token
5757
r.subrequest("/_refresh", "token=" + r.variables.refresh_token,
@@ -266,10 +266,43 @@ function validateIdToken(r) {
266266

267267
function logout(r) {
268268
r.log("OIDC logout for " + r.variables.cookie_auth_token);
269-
r.variables.session_jwt = "-";
270-
r.variables.access_token = "-";
271-
r.variables.refresh_token = "-";
272-
r.return(302, r.variables.oidc_logout_redirect);
269+
270+
// Determine if oidc_logout_redirect is a full URL or a relative path
271+
function getLogoutRedirectUrl(base, redirect) {
272+
return redirect.match(/^(http|https):\/\//) ? redirect : base + redirect;
273+
}
274+
275+
var logoutRedirectUrl = getLogoutRedirectUrl(r.variables.redirect_base, r.variables.oidc_logout_redirect);
276+
277+
// Helper function to perform the final logout steps
278+
function performLogout(redirectUrl) {
279+
r.variables.session_jwt = '-';
280+
r.variables.access_token = '-';
281+
r.variables.refresh_token = '-';
282+
r.return(302, redirectUrl);
283+
}
284+
285+
// Check if OIDC end session endpoint is available
286+
if (r.variables.oidc_end_session_endpoint) {
287+
288+
if (!r.variables.session_jwt || r.variables.session_jwt === '-') {
289+
if (r.variables.refresh_token && r.variables.refresh_token !== '-') {
290+
// Renew ID token if only refresh token is available
291+
auth(r, 0);
292+
} else {
293+
performLogout(logoutRedirectUrl);
294+
return;
295+
}
296+
}
297+
298+
// Construct logout arguments for RP-initiated logout
299+
var logoutArgs = "?post_logout_redirect_uri=" + encodeURIComponent(logoutRedirectUrl) +
300+
"&id_token_hint=" + encodeURIComponent(r.variables.session_jwt);
301+
performLogout(r.variables.oidc_end_session_endpoint + logoutArgs);
302+
} else {
303+
// Fallback to traditional logout approach
304+
performLogout(logoutRedirectUrl);
305+
}
273306
}
274307

275308
function getAuthZArgs(r) {
@@ -311,5 +344,5 @@ function idpClientAuth(r) {
311344
return "code=" + r.variables.arg_code + "&code_verifier=" + r.variables.pkce_code_verifier;
312345
} else {
313346
return "code=" + r.variables.arg_code + "&client_secret=" + r.variables.oidc_client_secret;
314-
}
347+
}
315348
}

openid_connect.server_conf

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
location = /_jwks_uri {
1010
internal;
11-
proxy_cache jwk; # Cache the JWK Set recieved from IdP
11+
proxy_cache jwk; # Cache the JWK Set received from IdP
1212
proxy_cache_valid 200 12h; # How long to consider keys "fresh"
1313
proxy_cache_use_stale error timeout updating; # Use old JWK Set if cannot reach IdP
1414
proxy_ssl_server_name on; # For SNI to the IdP
@@ -29,9 +29,9 @@
2929
# This location is called by the IdP after successful authentication
3030
status_zone "OIDC code exchange";
3131
js_content oidc.codeExchange;
32-
error_page 500 502 504 @oidc_error;
32+
error_page 500 502 504 @oidc_error;
3333
}
34-
34+
3535
location = /_token {
3636
# This location is called by oidcCodeExchange(). We use the proxy_ directives
3737
# to construct the OpenID Connect token request, as per:
@@ -68,8 +68,9 @@
6868

6969
location = /logout {
7070
status_zone "OIDC logout";
71-
add_header Set-Cookie "auth_token=; $oidc_cookie_flags"; # Send empty cookie
72-
add_header Set-Cookie "auth_redir=; $oidc_cookie_flags"; # Erase original cookie
71+
add_header Set-Cookie "auth_token=; $oidc_cookie_flags";
72+
add_header Set-Cookie "auth_nonce=; $oidc_cookie_flags";
73+
add_header Set-Cookie "auth_redir=; $oidc_cookie_flags";
7374
js_content oidc.logout;
7475
}
7576

openid_connect_configuration.conf

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,13 @@ map $host $oidc_jwt_keyfile {
2828
default "http://127.0.0.1:8080/auth/realms/master/protocol/openid-connect/certs";
2929
}
3030

31+
map $host $oidc_end_session_endpoint {
32+
# Specifies the end_session_endpoint URL for RP-initiated logout.
33+
# If this variable is empty or not set, the default behavior is maintained,
34+
# which logs out only on the NGINX side.
35+
default "";
36+
}
37+
3138
map $host $oidc_client {
3239
default "my-client-id";
3340
}

0 commit comments

Comments
 (0)