Skip to content

Commit d05e89d

Browse files
authored
Merge pull request #14 from nginx-openid-connect/009-access-token-new-endpoints
feat: access token & new endpoints (/login, /userinfo, /v2/logout)
2 parents bd4e9cb + de4bab2 commit d05e89d

6 files changed

+477
-85
lines changed

README.md

+147-71
Large diffs are not rendered by default.

configure.sh

+2-2
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_jwks_uri \(.jwks_uri)\n$oidc_end_session_endpoint \(.end_session_endpoint)\n$oidc_userinfo_endpoint \(.userinfo_endpoint)"' < /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,7 +178,7 @@ 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_jwt_keyfile \$oidc_end_session_endpoint \$oidc_userinfo_endpoint \$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 ..."

frontend.conf

+115
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,16 @@
1+
# -----------------------------------------------------------------------------#
2+
# #
3+
# Sample Reverse Proxy Configuration: Frontend Site, Backend App #
4+
# (for Open ID Connect workflow) #
5+
# #
6+
# -----------------------------------------------------------------------------#
7+
8+
# -----------------------------------------------------------------------------#
9+
# #
10+
# 1. Basic Example: Landing page starts OIDC workflow w/o login/logout button. #
11+
# #
12+
# -----------------------------------------------------------------------------#
13+
114
# This is the backend application we are protecting with OpenID Connect
215
upstream my_backend {
316
zone my_backend 64k;
@@ -33,4 +46,106 @@ server {
3346
}
3447
}
3548

49+
# -----------------------------------------------------------------------------#
50+
# #
51+
# 2. Advanced Example: Landing page, login/logout button to handle OIDC kflow #
52+
# #
53+
# - Landing page shows 'login' button #
54+
# - 'login' button calls `/login` endpoint to start OIDC flow by validating
55+
# 'id_token' w/ IdP's JWK. #
56+
# - Landing page calls `/userinfo` to show user info using 'access_token`. #
57+
# - 'logout' button to be finished OIDC session by IdP. #
58+
# - API authorization by validating `access_token` w/ IdP's JWK #
59+
# #
60+
# -----------------------------------------------------------------------------#
61+
62+
#
63+
# Upstream server for proxing to the frontend site.
64+
# - Example of a bundle frontend app to locally test NGINX Plus OIDC workflow.
65+
# https://github.com/nginx-openid-connect/nginx-oidc-examples/blob/main/001-oidc-local-test/docker/build-context/nginx/sample/proxy_server_frontend.conf
66+
# This link is subject to change.
67+
# - Modify this configuration to match your frontend site.
68+
#
69+
upstream my_frontend_site {
70+
zone my_frontend_site 64k;
71+
server 127.0.0.1:9091;
72+
}
73+
74+
#
75+
# Upstream sample for proxing to the backend API server.
76+
# - Example of a bundle backend app to locally test an API using access token.
77+
# + https://github.com/nginx-openid-connect/nginx-oidc-examples/blob/main/001-oidc-local-test/docker/build-context/nginx/sample/proxy_server_backend.conf
78+
# This link is subject to change.
79+
# - Modify this configuration to match your backend app.
80+
#
81+
upstream my_backend_app {
82+
zone my_backend_app 64k;
83+
server 127.0.0.1:9092;
84+
}
85+
86+
#
87+
# Sample Frontend-site & backend-api-server for the OIDC workflow.
88+
#
89+
server {
90+
# Enable when debugging is needed.
91+
error_log /var/log/nginx/error.log debug; # Reduce severity level as required
92+
access_log /var/log/nginx/access.log main;
93+
94+
# Replace the following server name with your host name.
95+
#
96+
# [Example: if you want to locally test OIDC in your laptop]
97+
# - Add '127.0.0.1 nginx.oidc.test` in your `/etc/hosts'.
98+
# - Use the command like 'make start'.
99+
# - Type 'https://nginx.oidc.test' in your browser.
100+
# - You will see the sample landing page and 'Sign In' button.
101+
#
102+
listen 8020; # Use SSL/TLS in production
103+
server_name nginx.oidc.test;
104+
105+
# Replace the following files with your certificate.
106+
ssl_certificate /etc/ssl/nginx/nginx-repo.crt;
107+
ssl_certificate_key /etc/ssl/nginx/nginx-repo.key;
108+
109+
# OIDC workflow
110+
include conf.d/openid_connect.server_conf;
111+
112+
#
113+
# Frontend example:
114+
#
115+
# - Default landing page: no need OIDC workflow to show 'Sign In' button.
116+
# - The site is protected with OpenID Connect(OIDC) by calling the API
117+
# endpoint of `/login` when users click 'login' button.
118+
#
119+
location / {
120+
proxy_pass http://my_frontend_site;
121+
access_log /var/log/nginx/access.log main_jwt;
122+
}
123+
124+
#
125+
# Backend API example to interact with proxied backend service:
126+
#
127+
# - This API resource is protected by access token which is received by IdP
128+
# after successful signing-in among the frontend site, NGINX Plus and IdP.
129+
#
130+
# - To ensure that client requests access the API securely, access token is
131+
# used for API authorization.
132+
# + Most of IdP generate an access token for API authorization of IdP's
133+
# endpoints (like /userinfo) as well as customer's endpoints.
134+
# + But Azure AD generate two types of access token for API authorization
135+
# of Microsoft graph API endpoints and customers' endpoints.
136+
# + Therefore, we recommend that you use $session_jwt for Azure AD and
137+
# $access_token for most of IdPs such as Cognito, Auth0, Keycloak, Okta,
138+
# OneLogin, Ping Identity, etc as for now.
139+
#
140+
location /v1/api/example {
141+
auth_jwt "" token=$access_token; # Use $session_jwt for Azure AD
142+
auth_jwt_key_request /_jwks_uri; # Enable when using URL
143+
#auth_jwt_key_file $oidc_jwt_keyfile; # Enable when using filename
144+
145+
proxy_set_header Authorization "Bearer $access_token";
146+
proxy_pass http://my_backend_app;
147+
access_log /var/log/nginx/access.log main_jwt;
148+
}
149+
}
150+
36151
# vim: syntax=nginx

openid_connect.js

+78-2
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,15 @@
55
*/
66
var newSession = false; // Used by oidcAuth() and validateIdToken()
77

8-
export default {auth, codeExchange, validateIdToken, logout};
8+
export default {
9+
auth,
10+
codeExchange,
11+
validateIdToken,
12+
logout,
13+
v2logout,
14+
redirectPostLogin,
15+
redirectPostLogout
16+
};
917

1018
function retryOriginalRequest(r) {
1119
delete r.headersOut["WWW-Authenticate"]; // Remove evidence of original failed auth_jwt
@@ -104,6 +112,7 @@ function auth(r, afterSyncCheck) {
104112
// ID Token is valid, update keyval
105113
r.log("OIDC refresh success, updating id_token for " + r.variables.cookie_auth_token);
106114
r.variables.session_jwt = tokenset.id_token; // Update key-value store
115+
r.variables.access_token = tokenset.access_token;
107116

108117
// Update refresh token (if we got a new one)
109118
if (r.variables.refresh_token != tokenset.refresh_token) {
@@ -187,6 +196,7 @@ function codeExchange(r) {
187196
// Add opaque token to keyval session store
188197
r.log("OIDC success, creating session " + r.variables.request_id);
189198
r.variables.new_session = tokenset.id_token; // Create key-value store entry
199+
r.variables.new_access_token = tokenset.access_token;
190200
r.headersOut["Set-Cookie"] = "auth_token=" + r.variables.request_id + "; " + r.variables.oidc_cookie_flags;
191201
r.return(302, r.variables.redirect_base + r.variables.cookie_auth_redir);
192202
}
@@ -256,6 +266,7 @@ function validateIdToken(r) {
256266
function logout(r) {
257267
r.log("OIDC logout for " + r.variables.cookie_auth_token);
258268
r.variables.session_jwt = "-";
269+
r.variables.access_token = "-";
259270
r.variables.refresh_token = "-";
260271
r.return(302, r.variables.oidc_logout_redirect);
261272
}
@@ -294,4 +305,69 @@ function idpClientAuth(r) {
294305
} else {
295306
return "code=" + r.variables.arg_code + "&client_secret=" + r.variables.oidc_client_secret;
296307
}
297-
}
308+
}
309+
310+
//
311+
// Redirect URI after logging in the IDP.
312+
function redirectPostLogin(r) {
313+
r.return(302, r.variables.redirect_base + getIDTokenArgsAfterLogin(r));
314+
}
315+
316+
//
317+
// Get query parameter of ID token after sucessful login:
318+
//
319+
// - For the variable of `returnTokenToClientOnLogin` of the APIM, this config
320+
// is only effective for /login endpoint. By default, our implementation MUST
321+
// not return any token back to the client app.
322+
// - If its configured it can send id_token in the request uri as
323+
// `?id_token=sdfsdfdsfs` after successful login.
324+
//
325+
//
326+
function getIDTokenArgsAfterLogin(r) {
327+
if (r.variables.return_token_to_client_on_login == 'id_token') {
328+
return '?id_token=' + r.variables.id_token;
329+
}
330+
return '';
331+
}
332+
333+
//
334+
// RP-Initiated or Custom Logout w/ Idp.
335+
//
336+
// - An RP requests that the Idp log out the end-user by redirecting the
337+
// end-user's User Agent to the Idp's Logout endpoint.
338+
// - TODO: Handle custom logout parameters if Idp doesn't support standard spec
339+
// of 'OpenID Connect RP-Initiated Logout 1.0'.
340+
//
341+
// - https://openid.net/specs/openid-connect-rpinitiated-1_0.html#RPLogout
342+
// - https://openid.net/specs/openid-connect-rpinitiated-1_0.html#RedirectionAfterLogout
343+
//
344+
function v2logout(r) {
345+
r.log("OIDC logout for " + r.variables.cookie_auth_token);
346+
var idToken = r.variables.session_jwt;
347+
var queryParams = getRPInitiatedLogoutArgs(r, idToken);
348+
349+
r.variables.request_id = '-';
350+
r.variables.session_jwt = '-';
351+
r.variables.access_token = '-';
352+
r.variables.refresh_token = '-';
353+
r.return(302, r.variables.oidc_end_session_endpoint + queryParams);
354+
}
355+
356+
//
357+
// Get query params for RP-initiated logout:
358+
//
359+
// - https://openid.net/specs/openid-connect-rpinitiated-1_0.html#RPLogout
360+
// - https://openid.net/specs/openid-connect-rpinitiated-1_0.html#RedirectionAfterLogout
361+
//
362+
function getRPInitiatedLogoutArgs(r, idToken) {
363+
return '?post_logout_redirect_uri=' + r.variables.redirect_base
364+
+ r.variables.oidc_logout_redirect_uri +
365+
'&id_token_hint=' + idToken;
366+
}
367+
368+
//
369+
// Redirect URI after logged-out from the IDP.
370+
//
371+
function redirectPostLogout(r) {
372+
r.return(302, r.variables.post_logout_return_uri);
373+
}

openid_connect.server_conf

+69-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
# Advanced configuration START
22
set $internal_error_message "NGINX / OpenID Connect login failure\n";
33
set $pkce_id "";
4-
resolver 8.8.8.8; # For DNS lookup of IdP endpoints;
4+
resolver 8.8.8.8; # For global DNS lookup of IDP endpoint
5+
# 127.0.0.11; # For local Docker DNS lookup
6+
7+
resolver_timeout 10s;
58
subrequest_output_buffer_size 32k; # To fit a complete tokenset response
69
gunzip on; # Decompress IdP responses if necessary
710
# Advanced configuration END
@@ -42,7 +45,7 @@
4245
proxy_set_body "grant_type=authorization_code&client_id=$oidc_client&$args&redirect_uri=$redirect_base$redir_location";
4346
proxy_method POST;
4447
proxy_pass $oidc_token_endpoint;
45-
}
48+
}
4649

4750
location = /_refresh {
4851
# This location is called by oidcAuth() when performing a token refresh. We
@@ -66,6 +69,70 @@
6669
error_page 500 502 504 @oidc_error;
6770
}
6871

72+
#
73+
# User information endpoint for the following purposes:
74+
# - Browser to periodically check if your are signed-in based on status code.
75+
# - Browser to show the signed-in user information.
76+
# - https://openid.net/specs/openid-connect-core-1_0.html#UserInfo
77+
#
78+
location = /userinfo {
79+
auth_jwt "" token=$access_token; # Access token for API authorization
80+
#auth_jwt_key_file $oidc_jwt_keyfile; # Enable when using filename
81+
auth_jwt_key_request /_jwks_uri; # Enable when using URL
82+
83+
proxy_ssl_server_name on; # For SNI to the IdP
84+
proxy_set_header Authorization "Bearer $access_token";
85+
proxy_pass $oidc_userinfo_endpoint;
86+
access_log /var/log/nginx/access.log main_jwt;
87+
}
88+
89+
#
90+
# Login endpoint to start OIDC flow when a user clicks 'login' button in the
91+
# landing page.
92+
#
93+
location = /login {
94+
# This location is called by UI for logging-in IDP using OpenID Connect.
95+
auth_jwt "" token=$session_jwt; # ID token for user authentication.
96+
error_page 401 = @do_oidc_flow;
97+
98+
#auth_jwt_key_file $oidc_jwt_keyfile; # Enable when using filename
99+
auth_jwt_key_request /_jwks_uri; # Enable when using URL
100+
101+
# Redirect to the the original URI of UI after successful login to IDP.
102+
js_content oidc.redirectPostLogin;
103+
access_log /var/log/nginx/access.log main_jwt;
104+
}
105+
106+
#
107+
# V2 Logout: The following features are added in the NGINX R29.
108+
# - The spec of RP-Initiated Logout is added.
109+
# - Sample logout page for your OIDC simulation.
110+
# - TODO: Custom logout parameters will be separately added.
111+
#
112+
location = /v2/logout {
113+
# This location is called by UI to handle OIDC logout with IDP as per:
114+
# https://openid.net/specs/openid-connect-rpinitiated-1_0.html#RPLogout
115+
status_zone "OIDC logout";
116+
js_content oidc.v2logout;
117+
}
118+
119+
location = /v2/_logout {
120+
# This location is the default value of $oidc_logout_redirect_uri (in case
121+
# it wasn't configured) called by IdP after closing user session in IdP.
122+
123+
# Clean cookies
124+
add_header Set-Cookie "auth_token=; $oidc_cookie_flags"; # Send empty cookie
125+
add_header Set-Cookie "auth_redir=; $oidc_cookie_flags"; # Erase original cookie
126+
add_header Set-Cookie "auth_nonce=; $oidc_cookie_flags";
127+
128+
# Redirect to either the original page or custom logout page.
129+
js_content oidc.redirectPostLogout;
130+
}
131+
132+
#
133+
# V1 Logout (NGINX R28):
134+
# - Need to implement 'RP-Initiated or Custom Logout' by yourselves.
135+
#
69136
location = /logout {
70137
status_zone "OIDC logout";
71138
add_header Set-Cookie "auth_token=; $oidc_cookie_flags"; # Send empty cookie

0 commit comments

Comments
 (0)