Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 39 additions & 29 deletions 001-oidc-local-test/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ This is to provide how to set up IdP, NGINX Plus and locally test OIDC flow in y
```bash
$ sudo vi /etc/hosts or notepad.exe c:\Windows\System32\Drivers\etc\hosts

127.0.0.1 nginx.keycloak.test
127.0.0.1 www.example.com
127.0.0.1 host.docker.internal
```

Expand Down Expand Up @@ -73,7 +73,7 @@ This is to provide how to set up IdP, NGINX Plus and locally test OIDC flow in y

## 3. Running a Browser and Checking If Bundle Page Works

- Run a Web Browser with `http://nginx.oidc.test:8020/`, and check if the bundle frontend landing page is shown:
- Run a Web Browser with `http://www.example.com:8010/`, and check if the bundle frontend landing page is shown:

![](./img/bundle-frontend-landing-page.png)

Expand All @@ -95,15 +95,15 @@ Choose one of your prefered IdPs, and set up your IdP by referencing the followi
> - **Client ID**: `my-client-id`
> - **Access Type**: `public` for PKCE
> - **Valid Redirected URIs**:
> - `http://nginx.oidc.test:8020/_codexch`
> - `http://nginx.oidc.test:8020/v2/_logout`
> - `http://www.example.com:8010/_codexch`
> - `http://www.example.com:8010/_logout`
> - The above references will be eventually consolidated into the [NGINX Docs](https://docs.nginx.com/nginx/deployment-guides/single-sign-on/). So feel free to contribute the repo to make better examples for each IdP as references.

## 5. Configuring NGINX Plus

Update the NGINX Plus configuration file if you want. Otherwise, skip the following steps for your quick test as the minimal config information is already provided in this repo.

- In the `openid_connect_configuration.conf`, find and update `$oidc_authz_endpoint`, `$oidc_token_endpoint`, `$oidc_jwt_keyfile`, `$oidc_end_session_endpoint`, `$oidc_userinfo_endpoint`, `$oidc_client`, `$oidc_pkce_enable`, `$oidc_client_secret`, and `$oidc_scopes` upon your setup in IdP.
- In the `openid_connect_configuration.conf`, find and update `$oidc_authz_endpoint`, `$oidc_token_endpoint`, `$oidc_jwt_keyfile`, `$oidc_logout_endpoint`, `$oidc_userinfo_endpoint`, `$oidc_client`, `$oidc_pkce_enable`, `$oidc_client_secret`, and `$oidc_scopes` upon your setup in IdP.

```nginx
map $host $oidc_authz_endpoint {
Expand All @@ -118,7 +118,7 @@ Update the NGINX Plus configuration file if you want. Otherwise, skip the follow
default "http://host.docker.internal:8080/auth/realms/master/protocol/openid-connect/certs";
}

map $host $oidc_end_session_endpoint {
map $host $oidc_logout_endpoint {
default "http://host.docker.internal:8080/auth/realms/master/protocol/openid-connect/logout";
}

Expand All @@ -142,26 +142,30 @@ Update the NGINX Plus configuration file if you want. Otherwise, skip the follow
default "openid+profile+email+offline_access";
}

map $host $oidc_landing_page {
# Where to send browser after successful login. This option is only
# recommended for scenarios where a landing page shows default information
# without login, and the RP redirects to the landing page after successful
# login from the OP. If this is empty, then the RP redirects to $request_uri.
default "";
www.example.com $redirect_base;
}

map $host $post_logout_return_uri {
# The following examples can be replaced with a custom logout page, or
# a complete URL to be redirected after successful logout from the IdP.

# Example 1: Redirect to the original langding page.
# ./docker/build-context/nginx/sample/proxy_server_frontend.conf
# -> redirect to the '/' location block
# ./docker/build-context/content/index.html
#
default $redirect_base;

# Example 2: Redirect to a custom logout page
# ./docker/build-context/nginx/sample/proxy_server_frontend.conf
# -> redirect to the '/signout' location block
# ./docker/build-context/content/signout.html
#
# default $redirect_base/signout;

# Example 3: Redirect to an another URL
# default https://www.nginx.com;
# Where to send browser after the RP requests /logout to the OP, and after
# the RP (/_logout) is called by the OP and cleans cookies. The following
# If this is empty, then the RP redirects to $request_uri.

default "";

# Edit if you want to redirect to the landing page
www.example.com $oidc_landing_page;

# Edit if you want to redirect to a custom logout page
#www.example.com $redirect_base/signout;

# Edit if you want to redirect to an another complete URL
#www.example.com https://www.nginx.com;
}
```

Expand Down Expand Up @@ -197,24 +201,30 @@ Update the NGINX Plus configuration file if you want. Otherwise, skip the follow
> - In the [`frontend.conf`](../frontend.conf), you can add additional API endpoints like:
>
> ```nginx
> location /v1/api/example {
> location /v1/private-api {
> auth_jwt "" token=$access_token; # Use $session_jwt for Azure AD
> auth_jwt_key_request /_jwks_uri; # Enable when using URL
>
> proxy_set_header Authorization "Bearer $access_token";
> proxy_pass http://my_backend_app;
> proxy_pass http://my_backend;
> }
> ```
>
> - So you can enter a different URI for testing your additional API endpoints via this bundled frontend tool.

- Click `Sign Out` button:

- Redirect to the original landing page if you configure `$post_logout_return_uri` with `$redirect_base` as the following example:
- Redirect to the original landing page if you configure `$post_logout_return_uri` with `$oidc_landing_page` as the following example:

```nginx
map $host $oidc_landing_page {
default "";
www.example.com $redirect_base;
}

map $host $post_logout_return_uri {
default $redirect_base;
default "";
www.example.com $oidc_landing_page;
}
```

Expand Down
Binary file modified 001-oidc-local-test/img/bundle-frontend-landing-page.png
100755 → 100644
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified 001-oidc-local-test/img/make-watch.png
100755 → 100644
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified 001-oidc-local-test/img/oidc-original-landing-page.png
100755 → 100644
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified 001-oidc-local-test/img/oidc-sample-proxied-api.png
100755 → 100644
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified 001-oidc-local-test/img/oidc-signed-in-page.png
100755 → 100644
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
19 changes: 13 additions & 6 deletions 001-oidc-local-test/openid_connect.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ function auth(r, afterSyncCheck) {
return;
}
// Redirect the client to the IdP login page with the cookies we need for state
r.return(302, r.variables.oidc_authz_endpoint + getAuthZArgs(r));
r.return(302, r.variables.oidc_authz_endpoint + getQueryParamsAuthZ(r));
return;
}

Expand Down Expand Up @@ -302,14 +302,15 @@ function logout(r) {
r.return(302, r.variables.oidc_logout_endpoint + queryParams);
}

function getAuthZArgs(r) {
function getQueryParamsAuthZ(r) {
// Choose a nonce for this flow for the client, and hash it for the IdP
var noncePlain = r.variables.request_id;
var c = require('crypto');
var h = c.createHmac('sha256', r.variables.oidc_hmac_key).update(noncePlain);
var nonceHash = h.digest('base64url');
var authZArgs = "?response_type=code&scope=" + r.variables.oidc_scopes + "&client_id=" + r.variables.oidc_client + "&redirect_uri="+ r.variables.redirect_base + r.variables.redir_location + "&nonce=" + nonceHash;
var queryParams = "?response_type=code&scope=" + r.variables.oidc_scopes + "&client_id=" + r.variables.oidc_client + "&redirect_uri="+ r.variables.redirect_base + r.variables.redir_location + "&nonce=" + nonceHash;

r.variables.nonce_hash = nonceHash;
r.headersOut['Set-Cookie'] = [
"auth_redir=" + r.variables.request_uri + "; " + r.variables.oidc_cookie_flags,
"auth_nonce=" + noncePlain + "; " + r.variables.oidc_cookie_flags
Expand All @@ -320,12 +321,18 @@ function getAuthZArgs(r) {
r.variables.pkce_id = c.createHash('sha256').update(String(Math.random())).digest('base64url');
var pkce_code_challenge = c.createHash('sha256').update(pkce_code_verifier).digest('base64url');
r.variables.pkce_code_verifier = pkce_code_verifier;
r.variables.pkce_code_challenge = pkce_code_challenge;

authZArgs += "&code_challenge_method=S256&code_challenge=" + pkce_code_challenge + "&state=" + r.variables.pkce_id;
queryParams += "&code_challenge_method=S256&code_challenge=" + pkce_code_challenge + "&state=" + r.variables.pkce_id;
} else {
authZArgs += "&state=0";
queryParams += "&state=0";
}
return authZArgs;
if (r.variables.oidc_authz_query_params_option == REPLACE_PARAMS) {
queryParams = '?' + r.variables.oidc_authz_query_params;
} else if (r.variables.oidc_authz_query_params_option == EXTRA_PARAMS) {
queryParams += '&' + r.variables.oidc_authz_query_params;
}
return queryParams;
}

function idpClientAuth(r) {
Expand Down
22 changes: 22 additions & 0 deletions 001-oidc-local-test/openid_connect_configuration.conf
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,24 @@ map $host $oidc_authz_endpoint {
#www.example.com "https://my-idp/oauth2/v1/authorize";
}

map $host $oidc_authz_query_params_option {
# The option of custom query params in the request of $oidc_authz_endpoint.
# 0: built-in params (e.g. response_type, client_id, redirect_uri, nonce)
# 1: extra args($oidc_authz_query_params) are extended after built-in params
# 2: replace built-in params with custom params($oidc_authz_query_params)
default 0;
}

map $host $oidc_authz_query_params {
# Each IdP may use different query params of the $oidc_authz_endpoint. For
# example, Keycloak can select an IdP to delegate to via the "kc_idp_hint"
# argument. It must be expressed as query string parameters and URL-encoded
# if required.
default "";
#extra.args.example "kc_idp_hint=another_provider";
#replace.args.example "response_type=code&scope=$oidc_scopes&client_id=$oidc_client&redirect_uri=$redirect_base$redir_location&nonce=$nonce_hash&state=0&audience=https://auth0.com/api/v2/";
}

map $host $oidc_token_endpoint {
default "http://host.docker.internal:8080/auth/realms/master/protocol/openid-connect/token";
}
Expand Down Expand Up @@ -139,6 +157,8 @@ keyval_zone zone=oidc_access_tokens:1M state=/var/lib/nginx/state/oidc_access_to
keyval_zone zone=refresh_tokens:1M state=/var/lib/nginx/state/refresh_tokens.json timeout=8h;
keyval_zone zone=oidc_pkce:128K timeout=90s; # Temporary storage for PKCE code verifier.
keyval_zone zone=oidc_userinfo:128K timeout=90s; # Temporary storage for user information.
keyval_zone zone=oidc_pkce_challenge:128K timeout=90s; # Temporary storage for PKCE code challenge.
keyval_zone zone=oidc_nonce:128K timeout=90s; # Temporary storage for nonce.

keyval $cookie_auth_token $session_jwt zone=oidc_id_tokens; # Exchange cookie for JWT
keyval $cookie_auth_token $access_token zone=oidc_access_tokens; # Exchange cookie for access token
Expand All @@ -147,7 +167,9 @@ keyval $request_id $new_session zone=oidc_id_tokens; # For initial
keyval $request_id $new_access_token zone=oidc_access_tokens;
keyval $request_id $new_refresh zone=refresh_tokens;
keyval $request_id $user_info zone=oidc_userinfo;
keyval $request_id $nonce_hash zone=oidc_nonce;
keyval $pkce_id $pkce_code_verifier zone=oidc_pkce;
keyval $pkce_id $pkce_code_challenge zone=oidc_pkce_challenge;

auth_jwt_claim_set $jwt_audience aud; # In case aud is an array
js_import oidc from conf.d/openid_connect.js;
Expand Down