Skip to content

Commit c8bd3b9

Browse files
committed
R18
1 parent cfd2cc2 commit c8bd3b9

File tree

9 files changed

+260
-439
lines changed

9 files changed

+260
-439
lines changed

README.md

Lines changed: 64 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ Reference implementation of NGINX Plus as relying party for OpenID Connect authe
44

55
## Description
66

7-
This repository describes how to enable OpenID Connect integration for [NGINX Plus](https://www.nginx.com/products/nginx/). The solution depends on the [auth_jwt](http://nginx.org/en/docs/http/ngx_http_auth_jwt_module.html) module and as such is not suitable for [open source NGINX](http://www.nginx.org/en).
7+
This repository describes how to enable OpenID Connect integration for [NGINX Plus](https://www.nginx.com/products/nginx/). The solution depends on NGINX Plus components ([auth_jwt module](http://nginx.org/en/docs/http/ngx_http_auth_jwt_module.html) and [key-value store](http://nginx.org/en/docs/http/ngx_http_keyval_module.html)) and as such is not suitable for [open source NGINX](http://www.nginx.org/en).
88

99
<img src=https://www.nginx.com/wp-content/uploads/2018/04/dia-LC-2018-03-30-OpenID-Connect-authorization-code-flow-NGINX-800x426-03.svg alt="OpenID Connect components" width=500>
1010

@@ -24,15 +24,23 @@ With this environment, both the client and NGINX Plus communicate directly with
2424

2525
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.
2626

27-
The ID Token received from the IdP is then [validated](https://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation). NGINX Plus then issues a session cookie to the client using either the ID Token or the Access Token and is redirected to the original URI requested prior to authentication.
27+
The ID Token received from the IdP is then [validated](https://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation). 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.
2828

29-
Subsequent requests to protected resources are authenticated using the session cookie by performing JWT validation.
30-
31-
For more information on OIDC and NGINX Plus JWT support, see [Authenticating Users to Existing Applications with OpenID Connect and NGINX Plus](https://www.nginx.com/blog/authenticating-users-existing-applications-openid-connect-nginx-plus/).
29+
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.
30+
31+
For more information on OpenID Connect and JWT validation with NGINX Plus, see [Authenticating Users to Existing Applications with OpenID Connect and NGINX Plus](https://www.nginx.com/blog/authenticating-users-existing-applications-openid-connect-nginx-plus/).
32+
33+
### Refresh Tokens
34+
35+
If a [refresh token](https://openid.net/specs/openid-connect-core-1_0.html#RefreshTokens) 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.
36+
37+
### Logout
38+
39+
Requests made to the `/logout` location invalidate both the ID 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.
3240

3341
## Installation
3442

35-
OpenID Connect integration requires NGINX Plus R15 or later to be installed. See [Installing NGINX Plus](https://docs.nginx.com/nginx/admin-guide/installing-nginx/installing-nginx-plus/).
43+
The master branch of this repo requires the most recent release of NGINX Plus. Older releases should use the branch corresponding to that NGINX Plus release. For installation instructions, see [Installing NGINX Plus](https://docs.nginx.com/nginx/admin-guide/installing-nginx/installing-nginx-plus/).
3644

3745
In addition, the [njs module](https://www.nginx.com/blog/introduction-nginscript/) 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:
3846

@@ -67,7 +75,7 @@ All files can be copied to **/etc/nginx/conf.d**
6775
* Make a note of the `client ID` and `client secret`
6876

6977
* 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 **frontend.conf** configuration. Otherwise:
70-
* Download the `jwks_uri` JWK file to your NGINX Plus instance
78+
* Obtain the URL for `jwks_uri` or download the JWK file to your NGINX Plus instance
7179
* Obtain the URL for the **authorization endpoint**
7280
* Obtain the URL for the **token endpoint**
7381

@@ -78,8 +86,10 @@ Review the following files copied from the GitHub repository so that they match
7886
* **frontend.conf** - this is the reverse proxy configuration and where the IdP is configured. This file can be automatically configured by using the `configure.sh` script.
7987
* Modify the upstream group to match your backend site or app
8088
* Modify the `resolver` directive to match a DNS server that is capable of resolving the IdP defined in `$oidc_token_endpoint`
89+
* Modify the URI defined in `$oidc_logout_redirect` to specify an unprotected resource to be displayed after requesting the `/logout` location
8190
* Configure the preferred listen port and [enable SSL/TLS configuration](https://docs.nginx.com/nginx/admin-guide/security-controls/terminating-ssl-http/)
82-
* Set the value of `$oidc_jwt_keyfile` to match the downloaded JWK file from the IdP and ensure that it is readable by the NGINX worker processes
91+
* Set the value of `$oidc_jwt_keyfile` to specify the `jwks_uri` value or match the JWK file downloaded from the IdP (ensuring that it is readable by the NGINX worker processes)
92+
* 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
8393
* Modify all of the `set $oidc_` directives to match your IdP configuration
8494
* Set a unique value for `$oidc_hmac_key` to ensure nonce values are unpredictable
8595

@@ -91,6 +101,49 @@ Review the following files copied from the GitHub repository so that they match
91101
* **openid_connect.js** - this is the JavaScript code for performing the authorization code exchange and nonce hashing
92102
* No changes are required unless modifying the code exchange or validation process
93103

104+
### Configuring the Key-Value Store
105+
106+
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.
107+
108+
```nginx
109+
keyval_zone zone=opaque_sessions:1M state=conf.d/opaque_sessions.json timeout=1h;
110+
keyval_zone zone=refresh_tokens:1M state=conf.d/refresh_tokens.json timeout=8h;
111+
```
112+
113+
Each of the `keyval_zone` parameters are described below.
114+
115+
* **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 JWT, so scale this value to exceed the number of unique users that may authenticate.
116+
117+
* **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.
118+
119+
* **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.
120+
121+
* **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](http://nginx.org/en/docs/stream/ngx_stream_zone_sync_module.html) and by adding the `sync` parameter to the `keyval_zone` directives above.
122+
123+
## Session Management
124+
125+
The [NGINX Plus API](http://nginx.org/en/docs/http/ngx_http_api_module.html) 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.
126+
127+
To query the current sessions in the key-value store:
128+
129+
```shell
130+
$ curl localhost:8010/api/4/http/keyvals/opaque_sessions
131+
```
132+
133+
To delete a single session:
134+
135+
```shell
136+
$ curl -iX PATCH -d '{"<session ID>":null}' localhost:8010/api/4/http/keyvals/opaque_sessions
137+
$ curl -iX PATCH -d '{"<session ID>":null}' localhost:8010/api/4/http/keyvals/refresh_tokens
138+
```
139+
140+
To delete all sessions:
141+
142+
```shell
143+
$ curl -iX DELETE localhost:8010/api/3/http/keyvals/opaque_sessions
144+
$ curl -iX DELETE localhost:8010/api/3/http/keyvals/refresh_tokens
145+
```
146+
94147
## Troubleshooting
95148

96149
Any errors generated by the OpenID Connect flow are logged in a separate file, `/var/log/nginx/oidc_error.log`. Check the contents of this file as it may include error responses received by the IdP.
@@ -102,21 +155,16 @@ Any errors generated by the OpenID Connect flow are logged in a separate file, `
102155
* **Authentication is successful but browser shows too many redirects**
103156
* 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.
104157
* Check the error log `/var/log/nginx/oidc_error.log` for JWT/JWK errors.
105-
* Ensure that the JWK file (`$oidc_jwt_keyfile` variable) is correct and that the nginx workers have permission to read it.
158+
* Ensure that the JWK file (`$oidc_jwt_keyfile` variable) is correct and that the nginx user has permission to read it.
106159

107160
## Support
108161

109-
All reference OpenID Connect implementations within the GitHub repository are supported for NGINX Plus subscribers.
110-
111-
## Other use cases
112-
113-
Subdirectories within the GitHub repository contain variations of the reference implementation for alternative OpenID Connect use cases.
162+
This reference implementation for OpenID Connect is supported for NGINX Plus subscribers.
114163

115-
* **opaque_session_token** - Uses the NGINX Plus key-value store to hold the ID Token, sending a random string to the client as the session token. The session token is then exchanged for the ID Token on each request. This use case is valuable when the ID Token contains sensitive information that should not reach the client.
116-
117164
## Changelog
118165

119166
* **R15** Initial release of OpenID Connect reference implmentation
120167
* **R16** Added support for opaque session tokens using key-value store
121168
* **R17** Configuration now supports JSON Web Key (JWK) set to be obtained by URI
169+
* **R18** Opaque session tokens now used by default. Added support for refresh tokens. Added `/logout` location.
122170

configure.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
COMMAND=${0##*/}
55
CONFDIR=${0%/*}
66
if [ $# -lt 1 ]; then
7-
echo "USAGE: $COMMAND [options] <OpenID Connect confinguration URL>"
7+
echo "USAGE: $COMMAND [options] <OpenID Connect configuration URL>"
88
echo ""
99
echo "Configures NGINX Plus OpenID Connect reference implementation by using the IdP's Discovery interface"
1010
echo ""

frontend.conf

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,23 @@ upstream my_backend {
55
}
66

77
# Custom log format to include the 'sub' claim in the REMOTE_USER field
8-
log_format main_jwt '$remote_addr $jwt_claim_sub $remote_user [$time_local] "$request" $status '
8+
log_format main_jwt '$remote_addr - $jwt_claim_sub [$time_local] "$request" $status '
99
'$body_bytes_sent "$http_referer" "$http_user_agent" "$http_x_forwarded_for"';
1010

11-
# nginScript functions for code exchange and hashing for secure nonce validation
11+
# JavaScript code for OpenID Connect
1212
js_include conf.d/openid_connect.js;
1313
js_set $requestid_hash hashRequestId;
14-
js_set $auth_token getAuthToken;
1514

16-
# Caching for JWT keys when using 'auth_jwt_key_request' with NGINX Plus R17+
17-
proxy_cache_path /var/cache/nginx/jwk levels=1 keys_zone=jwk:1m max_size=10m;
15+
keyval_zone zone=opaque_sessions:1M state=conf.d/opaque_sessions.json timeout=1h; # CHANGE timeout to JWT/exp validity period
16+
keyval_zone zone=refresh_tokens:1M state=conf.d/refresh_tokens.json timeout=8h; # CHANGE timeout to refresh validity period
17+
18+
keyval $cookie_auth_token $session_jwt zone=opaque_sessions; # Exchange cookie for JWT
19+
keyval $cookie_auth_token $refresh_token zone=refresh_tokens; # Exchange cookie for refresh token
20+
keyval $request_id $new_session zone=opaque_sessions; # For initial session creation
21+
keyval $request_id $new_refresh zone=refresh_tokens; # "
22+
23+
# JWK Set will be fetched from $oidc_jwks_uri and cached here - ensure writable by nginx user
24+
proxy_cache_path /var/cache/nginx/jwk levels=1 keys_zone=jwk:64k max_size=1m;
1825

1926
# The frontend server - reverse proxy with OpenID Connect authentication
2027
#
@@ -26,31 +33,30 @@ server {
2633
subrequest_output_buffer_size 32k; # To fit a complete tokenset response
2734

2835
set $oidc_jwt_keyfile /etc/nginx/my_idp_jwk.json; # URL when using 'auth_jwt_key_request'
36+
set $oidc_logout_redirect "/_logout"; Where to send browser after requesting /logout location
2937
set $oidc_authz_endpoint "http://127.0.0.1:8080/auth/realms/master/protocol/openid-connect/auth";
3038
set $oidc_token_endpoint "http://127.0.0.1:8080/auth/realms/master/protocol/openid-connect/token";
3139
set $oidc_client "my-client-id";
3240
set $oidc_client_secret "my-client-secret";
33-
set $oidc_token_type "id_token"; # Session token (access_token or id_token)
3441
set $oidc_hmac_key "ChangeMe"; # This should be unique for every NGINX instance/cluster
3542

3643
listen 8010; # Use SSL/TLS in production
37-
44+
3845
location / {
3946
# This site is protected with OpenID Connect
40-
auth_jwt "" token=$cookie_auth_token;
41-
auth_jwt_key_file $oidc_jwt_keyfile;
42-
#auth_jwt_key_request /_jwks_uri; # Requires NGINX Plus R17+
47+
auth_jwt "" token=$session_jwt;
48+
auth_jwt_key_file $oidc_jwt_keyfile; # Enable when using filename
49+
#auth_jwt_key_request /_jwks_uri; # Enable when using URL
4350

44-
# Absent/invalid OpenID Connect token will (re)start auth process
51+
# Absent/invalid OpenID Connect token will (re)start auth process (including refresh)
4552
error_page 401 @oidc_auth;
4653

47-
# Successfully authenticated users are proxied to the backend,
54+
# Successfuly authenticated users are proxied to the backend,
4855
# with 'sub' claim passed as HTTP header
4956
proxy_set_header username $jwt_claim_sub;
5057
proxy_pass http://my_backend; # The backend site/app
51-
58+
5259
access_log /var/log/nginx/access.log main_jwt;
53-
error_log /var/log/nginx/oidc_error.log info;
5460
}
5561
}
5662

opaque_session_token/README.md

Lines changed: 0 additions & 47 deletions
This file was deleted.

0 commit comments

Comments
 (0)