From 89dcc365f28acd636aad3db0ba3e1a29d57cbedf Mon Sep 17 00:00:00 2001 From: Ivan Ovchinnikov Date: Thu, 13 Mar 2025 07:23:30 -0700 Subject: [PATCH 01/12] Refactor OIDC Docs to use http_oidc_module. --- .../active-directory-federation-services.md | 363 +++++++++++++----- .../deployment-guides/single-sign-on/auth0.md | 351 +++++++++++------ .../single-sign-on/cognito.md | 318 +++++++++------ .../single-sign-on/entra-id.md | 297 ++++++++++++++ .../single-sign-on/keycloak.md | 335 +++++++++++----- .../single-sign-on/oidc-njs/_index.md | 9 + .../active-directory-federation-services.md | 157 ++++++++ .../single-sign-on/oidc-njs/auth0.md | 187 +++++++++ .../single-sign-on/oidc-njs/cognito.md | 193 ++++++++++ .../single-sign-on/oidc-njs/keycloak.md | 172 +++++++++ .../single-sign-on/oidc-njs/okta.md | 188 +++++++++ .../single-sign-on/oidc-njs/onelogin.md | 159 ++++++++ .../single-sign-on/oidc-njs/ping-identity.md | 197 ++++++++++ .../deployment-guides/single-sign-on/okta.md | 356 +++++++++++------ .../single-sign-on/onelogin.md | 325 +++++++++++----- .../single-sign-on/ping-identity.md | 340 ++++++++++------ 16 files changed, 3197 insertions(+), 750 deletions(-) create mode 100644 content/nginx/deployment-guides/single-sign-on/entra-id.md create mode 100644 content/nginx/deployment-guides/single-sign-on/oidc-njs/_index.md create mode 100644 content/nginx/deployment-guides/single-sign-on/oidc-njs/active-directory-federation-services.md create mode 100644 content/nginx/deployment-guides/single-sign-on/oidc-njs/auth0.md create mode 100644 content/nginx/deployment-guides/single-sign-on/oidc-njs/cognito.md create mode 100644 content/nginx/deployment-guides/single-sign-on/oidc-njs/keycloak.md create mode 100644 content/nginx/deployment-guides/single-sign-on/oidc-njs/okta.md create mode 100644 content/nginx/deployment-guides/single-sign-on/oidc-njs/onelogin.md create mode 100644 content/nginx/deployment-guides/single-sign-on/oidc-njs/ping-identity.md diff --git a/content/nginx/deployment-guides/single-sign-on/active-directory-federation-services.md b/content/nginx/deployment-guides/single-sign-on/active-directory-federation-services.md index 2243b6225..775323273 100644 --- a/content/nginx/deployment-guides/single-sign-on/active-directory-federation-services.md +++ b/content/nginx/deployment-guides/single-sign-on/active-directory-federation-services.md @@ -1,153 +1,328 @@ --- -description: Enable OpenID Connect-based single-sign for applications proxied by NGINX - Plus, using Microsoft AD FS as the identity provider (IdP). -docs: DOCS-463 +description: Enable OpenID Connect-based single sign-on (SSO) for applications proxied by NGINX Plus, using Microsoft AD FS as the identity provider (IdP). +doctypes: +- task title: Single Sign-On with Microsoft Active Directory FS toc: true -weight: 100 -type: -- how-to +weight: 300 --- -This guide explains how to enable single sign-on (SSO) for applications being proxied by F5 NGINX Plus. The solution uses OpenID Connect as the authentication mechanism, with [Microsoft Active Directory Federation Services](https://docs.microsoft.com/en-us/windows-server/identity/active-directory-federation-services) (AD FS) as the identity provider (IdP) and NGINX Plus as the relying party. +This guide explains how to enable single sign-on (SSO) for applications being proxied by F5 NGINX Plus. The solution uses OpenID Connect as the authentication mechanism, with [Microsoft Active Directory Federation Services](https://docs.microsoft.com/en-us/windows-server/identity/active-directory-federation-services) (AD FS) as the Identity Provider (IdP) and NGINX Plus as the Relying Party (RP), or OIDC client application that verifies user identity. -{{< see-also >}}{{< include "nginx-plus/nginx-openid-repo-note.txt" >}}{{< /see-also >}} +{{< note >}} This guide applies to [NGINX Plus Release 34]({{< ref "nginx/releases.md#r34" >}}) and later. In earlier versions, NGINX Plus relied on an [njs-based solution](#legacy-njs-guide), which required NGINX JavaScript files, key-value stores, and advanced OpenID Connect logic. In the latest NGINX Plus version, the new [OpenID Connect module](https://nginx.org/en/docs/http/ngx_http_oidc_module.html) simplifies this process to just a few directives.{{< /note >}} - -## Prerequisites - -The instructions assume you have the following: - -- A running deployment of AD FS, either on‑premises or in Azure. -- An NGINX Plus subscription and NGINX Plus R15 or later. For installation instructions, see the [NGINX Plus Admin Guide](https://docs.nginx.com/nginx/admin-guide/installing-nginx/installing-nginx-plus/). -- The [NGINX JavaScript module](https://www.nginx.com/blog/introduction-nginscript/) (njs), required for handling the interaction between NGINX Plus and the IdP. After installing NGINX Plus, install the module with the command for your operating system. - For Debian and Ubuntu: - - ```none - sudo apt install nginx-plus-module-njs - ``` +## Prerequisites - For CentOS, RHEL, and Oracle Linux: +- A Microsoft AD FS instance, either on-premises or in [Azure](https://learn.microsoft.com/en-us/windows-server/identity/ad-fs/deployment/how-to-connect-fed-azure-adfs), with administrator privileges. - ```shell - sudo yum install nginx-plus-module-njs - ``` +- An NGINX Plus [subscription](https://www.f5.com/products/nginx/nginx-plus) and NGINX Plus [Release 34](({{< ref "nginx/releases.md#r34" >}})) or later. For installation instructions, see [Installing NGINX Plus](https://docs.nginx.com/nginx/admin-guide/installing-nginx/installing-nginx-plus/). -- The following directive included in the top-level ("main") configuration context in **/etc/nginx/nginx.conf**, to load the NGINX JavaScript module: +- A domain name pointing to your NGINX Plus instance, for example, `demo.example.com`. - ```nginx - load_module modules/ngx_http_js_module.so; - ``` - -## Configuring AD FS +## Configure the AD FS Server {#adfs-setup} -Create an AD FS application for NGINX Plus: +[Microsoft Active Directory Federation Services](https://docs.microsoft.com/en-us/windows-server/identity/active-directory-federation-services) (AD FS) serves as the Identity Provider. -1. Open the AD FS Management window. In the navigation column on the left, right‑click on the **Application Groups** folder and select Add Application Group from the drop‑down menu. +### Create an AD FS Application - The Add Application Group Wizard window opens. The left navigation column shows the steps you will complete to add an application group. +1. In AD FS, open the Server Manager. -2. In the **Welcome** step, type the application group name in the **Name** field. Here we are using ADFSSSO. In the **Template** field, select **Server application** under Standalone applications. Click the  Next >  button. +2. In Server Manager, select **Tools**, and then select **AD FS Management**. - +3. In **AD FS Management**, right-click on **Application Groups** and select **Add Application Group**. - -3. In the **Server application** step: +4. On the Application Group Wizard **Welcome** screen: - 1. Make a note of the value in the **Client Identifier** field. You will add it to the NGINX Plus configuration in [Step 4 of _Configuring NGINX Plus_](#nginx-plus-variables).
+ - Enter the Name of your application, for example, `NGINX Demo App`. - 2. In the **Redirect URI** field, type the URI of the NGINX Plus instance including the port number, and ending in **/\_codexch**. Here we’re using https://my-nginx.example.com:443/\_codexch. Click the  Add  button. + - Under **Standalone applications**, select **Server application**. - **Notes:** + +5. On the Application Group Wizard **Server application** screen: - - For production, we strongly recommend that you use SSL/TLS (port 443). - - The port number is mandatory even when you're using the default port for HTTP (80) or HTTPS (443). + - Copy the **Client Identifier** value generated by AD FS. The client identifier is your AD FS Application ID, you will need it later when configuring NGINX Plus. -3. Click the  Next >  button. + - In **Redirect URI**, enter the Redirect URI for your NGINX Plus instance, for example, `https://demo.example.com/oidc_callback`, and then click **Add**. - + +6. On the Application Group Wizard **Configure Application Credentials** screen: - -4. In the Configure Application Credentials step, click the Generate a shared secret checkbox. Make a note of the secret that AD FS generates (perhaps by clicking the Copy to clipboard button and pasting the clipboard content into a file). You will add the secret to the NGINX Plus configuration in [Step 4 of _Configuring NGINX Plus_](#nginx-plus-variables). Click the  Next >  button. + - Select **Generate a shared secret**. - + - Copy and save the generated **Client Secret**, you will need it later when configuring NGINX Plus. You will not be able to view the secret after the application group is created. -5. In the **Summary** step, verify that the information is correct, make any necessary corrections to previous steps, and click the  Next >  button. + - Select **Next** to complete the steps for adding the application group. +### Get the OpenID Connect Discovery URL - -## Configuring NGINX Plus +Check the OpenID Connect endpoint URL. By default, AD FS publishes the `.well-known/openid-configuration` document at the following address: -Configure NGINX Plus as the OpenID Connect relying party: +`https://adfs-server-address/adfs/.well-known/openid-configuration`. -1. Create a clone of the [nginx-openid-connect](https://github.com/nginxinc/nginx-openid-connect) GitHub repository. +1. Run the following `curl` command in a terminal: ```shell - git clone https://github.com/nginxinc/nginx-openid-connect + curl https://adfs-server-address/adfs/.well-known/openid-configuration | jq ``` + where: -2. Copy these files from the clone to **/etc/nginx/conf.d**: + - the `adfs-server-address` is your AD FS server address - - **frontend.conf** - - **openid\_connect.js** - - **openid\_connect.server\_conf** - - **openid\_connect\_configuration.conf** + - the `/adfs/.well-known/openid-configuration` is the default address for AD FS for document location - -3. Get the URLs for the authorization endpoint, token endpoint, and JSON Web Key (JWK) file from the AD FS configuration. Run the following `curl` command in a terminal, piping the output to the indicated `python` command to output the entire configuration in an easily readable format. We've abridged the output to show only the relevant fields. + - the `jq` command (optional) is used to format the JSON output for easier reading and requires the [jq](https://jqlang.github.io/jq/) JSON processor to be installed. - ```shell - $ curl https:///oidc/adfs/.well-known/openid-configuration | python -m json.tool + + The configuration metadata is returned in the JSON format: + + ```json { - ... - "authorization_endpoint": "https:///oidc/adfs/auth", ... - "jwks_uri": "https:///oidc/adfs/certs", + "issuer": "https://adfs-server-address/adfs", + "authorization_endpoint": "https://adfs-server-address/adfs/oauth2/authorize/", + "token_endpoint": "https://adfs-server-address/adfs/oauth2/token/", + "jwks_uri": "https://adfs-server-address/adfs/discovery/keys", ... - "token_endpoint": "https:///oidc/adfs/token", - ... } ``` - -4. In your preferred text editor, open **/etc/nginx/conf.d/frontend.conf**. Change the "default" parameter value of each of the following [map](https://nginx.org/en/docs/http/ngx_http_map_module.html#map) directives to the specified value: + +2. Copy the **issuer** value, you will need it later when configuring NGINX Plus. Typically, the OpenID Connect Issuer for AD FS is: + + `https://adfs-server-address/adfs`. + + +{{< note >}} You will need the values of **Client ID**, **Client Secret**, and **Issuer** in the next steps. {{< /note >}} + + +## Set up NGINX Plus {#nginx-plus-setup} + +With AF DS configured, you can enable OIDC on NGINX Plus. NGINX Plus serves as the Rely Party (RP) application — a client service that verifies user identity. + + +1. Ensure that you are using the latest version of NGINX Plus by running the `nginx -v` command in a terminal: + + ```shell + nginx -v + ``` + The output should match NGINX Plus Release 34 or later: + + ```none + nginx version: nginx/1.27.4 (nginx-plus-r34) + ``` + +2. Ensure that you have the values of the **Client ID**, **Client Secret**, and **Issuer** obtained during [AD FS Configuration](#adfs-setup). + +3. In your preferred text editor, open the NGINX configuration file (`/etc/nginx/nginx.conf` for Linux or `/usr/local/etc/nginx/nginx.conf` for FreeBSD). + +4. In the [`http {}`](https://nginx.org/en/docs/http/ngx_http_core_module.html#http) context, make sure your public DNS resolver is specified with the [`resolver`](https://nginx.org/en/docs/http/ngx_http_core_module.html#resolver) directive. By default, NGINX Plus re‑resolves DNS records at the frequency specified by time‑to‑live (TTL) in the record, but you can override the TTL value with the `valid` parameter: + + ```nginx + http { + resolver 10.0.0.1 ipv4=on valid=300s; + + # ... + } + ``` + +5. In the [`http {}`](https://nginx.org/en/docs/http/ngx_http_core_module.html#http) context, define the AD FS OIDC provider named `adfs` by specifying the [`oidc_provider {}`](https://nginx.org/en/docs/http/ngx_http_oidc_module.html#oidc_provider) context: + + ```nginx + http { + resolver 10.0.0.1 ipv4=on valid=300s; + + oidc_provider adfs { + + # ... + + } + # ... + } + ``` + +6. In the [`oidc_provider {}`](https://nginx.org/en/docs/http/ngx_http_oidc_module.html#oidc_provider) context, specify: + + - your actual AD FS **Client ID** from [Step 5](#adfs-setup-id) of AD FS Configuration with the [`client_id`](https://nginx.org/en/docs/http/ngx_http_oidc_module.html#client_id) directive + + - your **Client Secret** from [Step 6](#adfs-setup-secret) of AD FS Configuration with the [`client_secret`](https://nginx.org/en/docs/http/ngx_http_oidc_module.html#client_secret) directive + + - the **Issuer** URL from [Step 2](#adfs-setup-issuer) of AD FS Configuration with the [`issuer`](https://nginx.org/en/docs/http/ngx_http_oidc_module.html#client_secret) directive + + The `issuer` is typically your AD FS OIDC URL. By default, NGINX forms the provider metadata endpoint by appending `.well-known/openid-configuration` to the issuer. For AD FS, this often resolves to `https://adfs-server-address/adfs/.well-known/openid-configuration`. If your AD FS issuer differs from `https://adfs-server-address/adfs` (for example, a custom path), you can explicitly specify the metadata document with the [`config_url`](https://nginx.org/en/docs/http/ngx_http_oidc_module.html#config_url) directive. + + - **Important:** All interaction with the IdP is secured exclusively over SSL/TLS, so NGINX must trust the certificate presented by the IdP. By default, this trust is validated against your system’s CA bundle (the default CA store for your Linux or FreeBSD distribution). If the IdP’s certificate is not included in the system CA bundle, you can explicitly specify a trusted certificate or chain with the [`ssl_trusted_certificate`](https://nginx.org/en/docs/http/ngx_http_oidc_module.html#ssl_trusted_certificate) directive so that NGINX can validate and trust the IdP’s certificate. + + ```nginx + http { + resolver 10.0.0.1 ipv4=on valid=300s; + + oidc_provider adfs { + issuer https://adfs.example.com/adfs; + client_id ; + client_secret ; + } + + # ... + } + ``` + +7. Make sure you have configured a [server](https://nginx.org/en/docs/http/ngx_http_core_module.html#server) that corresponds to `demo.example.com`, and there is a [location](https://nginx.org/en/docs/http/ngx_http_core_module.html#location) that [points](https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_pass) to your application (see [Step 10](#oidc_app)) at `http://127.0.0.1:8080` that is going to be OIDC-protected: + + ```nginx + http { + + # ... + + server { + listen 443 ssl; + server_name demo.example.com; + + ssl_certificate /etc/ssl/certs/fullchain.pem; + ssl_certificate_key /etc/ssl/private/key.pem; + + location / { + + # ... + + proxy_pass http://127.0.0.1:8080; + } + } + # ... + } + ``` + +8. Protect this [location](https://nginx.org/en/docs/http/ngx_http_core_module.html#location) with AD FS OIDC by specifying the [`auth_oidc`](https://nginx.org/en/docs/http/ngx_http_oidc_module.html#auth_oidc) directive that will point to the `afds` configuration specified in the [`oidc_provider {}`](https://nginx.org/en/docs/http/ngx_http_oidc_module.html#oidc_provider) context in [Step 5](#adfs-setup-oidc-provider): + + ```nginx + # ... + location / { + + auth_oidc adfs; + + # ... + + proxy_pass http://127.0.0.1:8080; + + } + # ... + ``` + +9. Pass the OIDC claims as headers to the application ([Step 10](#oidc_app)) with the [`proxy_set_header`](https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_set_header) directive. These claims are extracted from the ID token returned by AD FS: + + - [`$oidc_claim_sub`](https://nginx.org/en/docs/http/ngx_http_oidc_module.html#var_oidc_claim_) - a unique `Subject` identifier assigned for each user by AD FS + + - [`$oidc_claim_email`](https://nginx.org/en/docs/http/ngx_http_oidc_module.html#var_oidc_claim_) the e-mail address of the user + + - [`$oidc_claim_name`](https://nginx.org/en/docs/http/ngx_http_oidc_module.html#var_oidc_claim_) - the full name of the user + + - any other OIDC claim using the [`$oidc_claim_ `](https://nginx.org/en/docs/http/ngx_http_oidc_module.html#var_oidc_claim_) variable + + ```nginx + # ... + location / { + + auth_oidc adfs; + + proxy_set_header sub $oidc_claim_sub; + proxy_set_header email $oidc_claim_email; + proxy_set_header name $oidc_claim_name; + + proxy_pass http://127.0.0.1:8080; + } + # ... + ``` + + +10. Create a simple test application referenced by the `proxy_pass` directive which returns the authenticated user's full name and email upon successful authentication: + + ```nginx + # ... + server { + listen 8080; + + location / { + return 200 "Hello, $http_name!\nEmail: $http_email\nAD FS sub: $http_sub\n"; + default_type text/plain; + } + } + ``` +11. Save the NGINX configuration file and reload the configuration: + ```nginx + nginx -s reload + ``` + +### Complete Example + +This configuration example summarizes the steps outlined above. It includes only essential settings such as specifying the DNS resolver, defining the OIDC provider, configuring SSL, and proxying requests to an internal server. + +```nginx +http { + # Use a public DNS resolver for Issuer discovery, etc. + resolver 10.0.0.1 ipv4=on valid=300s; + + oidc_provider adfs { + + # The 'issuer' is typically your AD FS OIDC URL + # e.g. https://adfs.example.com/adfs + issuer https://adfs.example.com/adfs; + + # Replace with your actual AD FS Client ID and Secret + client_id ; + client_secret ; + } + + server { + listen 443 ssl; + server_name demo.example.com; + + ssl_certificate /etc/ssl/certs/fullchain.pem; + ssl_certificate_key /etc/ssl/private/key.pem; + + location / { + # Protect this location with AD FS OIDC + auth_oidc adfs; + + # Forward OIDC claims as headers if desired + proxy_set_header sub $oidc_claim_sub; + proxy_set_header email $oidc_claim_email; + proxy_set_header name $oidc_claim_name; + + proxy_pass http://127.0.0.1:8080; + } + } + + server { + listen 8080; - - `map $host $oidc_authz_endpoint` – Value of `authorization_endpoint` from [Step 3](#nginx-plus-urls) (in this guide, `https:///oidc/adfs/auth`) - - `map $host $oidc_token_endpoint` – Value of `token_endpoint` from [Step 3](#nginx-plus-urls) (in this guide, `https:///oidc/adfs/token`) - - `map $host $oidc_client` – Value in the **Client ID** field from [Step 3 of _Configuring AD FS_](#ad-fs-server-application) (in this guide, `3e23f0eb-9329-46ff-9d37-6ad24afdfaeb`) - - `map $host $oidc_client_secret` – Value in the **Client secret** field from [Step 4 of _Configuring AD FS_](#ad-fs-configure-application-credentials) (in this guide, `NUeuULtSCjgXTGSkq3ZwEeCOiig4-rB2XiW_W`) - - `map $host $oidc_hmac_key` – A unique, long, and secure phrase + location / { + return 200 "Hello, $http_name!\nEmail: $http_email\nAD FS sub: $http_sub\n"; + default_type text/plain; + } + } +} +``` -5. Configure the JWK file. The procedure depends on which version of NGINX Plus you are using. +### Testing - - In NGINX Plus R17 and later, NGINX Plus can read the JWK file directly from the URL reported as `jwks_uri` in [Step 3](#nginx-plus-urls). Change **/etc/nginx/conf.d/frontend.conf** as follows: +1. Open `https://demo.example.com/` in a browser. You will be automatically redirected to the AD FS sign-in page. - 1. Comment out (or remove) the [auth_jwt_key_file](http://nginx.org/en/docs/http/ngx_http_auth_jwt_module.html#auth_jwt_key_file) directive. - 2. Uncomment the [auth_jwt_key_request](http://nginx.org/en/docs/http/ngx_http_auth_jwt_module.html#auth_jwt_key_request) directive. (Its parameter, `/_jwks_uri`, refers to the value of the `$oidc_jwt_keyfile` variable, which you set in the next step.) - 3. Change the second parameter of the `set $oidc_jwt_keyfile` directive to the value reported in the `jwks_uri` field in [Step 3](#nginx-plus-urls) (in this guide, `https:///oidc/adfs/certs`). +2. Enter valid AD FS credentials of a user who has access the application. Upon successful sign-in, AD FS redirects you back to NGINX Plus, and you will see the proxied application content (for example, “Hello, Jane Doe!”). - - In NGINX Plus R16 and earlier, the JWK file must be on the local disk. (You can also use this method with NGINX Plus R17 and later if you wish.) - 1. Copy the JSON contents from the JWK file named in the `jwks_uri` field in [Step 3](#nginx-plus-urls) (in this guide, `https:///oidc/adfs/certs`) to a local file (for example, `/etc/nginx/my_adfs_jwk.json`). - 2. In **/etc/nginx/conf.d/frontend.conf**, change the second parameter of the `set $oidc_jwt_keyfile` directive to the local file path. +## Legacy njs-based AD FS Solution {#legacy-njs-guide} -6. Confirm that the user named by the [user](http://nginx.org/en/docs/ngx_core_module.html#user) directive in the NGINX Plus configuration (in **/etc/nginx/nginx.conf** by convention) has read permission on the JWK file. +If you are running NGINX Plus R33 and earlier or if you still need the njs-based solution, refer to the [Legacy njs-based Microsoft AD FS Guide]({{< ref "nginx/deployment-guides/single-sign-on/oidc-njs/active-directory-federation-services.md" >}}) for details. The solution uses the [`nginx-openid-connect`](https://github.com/nginxinc/nginx-openid-connect) GitHub repository and NGINX JavaScript files. - -## Testing -In a browser, enter the address of your NGINX Plus instance and try to log in using the credentials of a user who has access to the application. +## See Also - +- [NGINX Plus Native OIDC Module Reference documentation](https://nginx.org/en/docs/http/ngx_http_oidc_module.html) - -## Troubleshooting +- [Release Notes for NGINX Plus R34]({{< ref "nginx/releases.md#r34" >}}) -See the [**Troubleshooting**](https://github.com/nginxinc/nginx-openid-connect#troubleshooting) section at the nginx-openid-connect repository on GitHub. -### Revision History +## Revision History -- Version 2 (March 2020) – Updates to _Configuring NGINX Plus_ section -- Version 1 (December 2019) – Initial version (NGINX Plus Release 20) +- Version 1 (March 2025) – Initial version (NGINX Plus Release 34) diff --git a/content/nginx/deployment-guides/single-sign-on/auth0.md b/content/nginx/deployment-guides/single-sign-on/auth0.md index a06a98983..5937dc5a5 100644 --- a/content/nginx/deployment-guides/single-sign-on/auth0.md +++ b/content/nginx/deployment-guides/single-sign-on/auth0.md @@ -1,186 +1,313 @@ --- -description: Learn how to enable single sign-on (SSO) with [Auth0](https://auth0.com/) - for applications proxied by F5 NGINX Plus. -docs: DOCS-884 +description: Enable OpenID Connect-based single sign-on (SSO) for applications proxied by NGINX Plus, using Auth0 as the identity provider (IdP). +doctypes: +- task title: Single Sign-On With Auth0 toc: true weight: 100 -type: -- tutorial --- -
+This guide explains how to enable single sign-on (SSO) for applications being proxied by F5 NGINX Plus. The solution uses OpenID Connect as the authentication mechanism, with [Auth0](https://auth0.com/features/single-sign-on) as the Identity Provider (IdP), and NGINX Plus as the Relying Party, or OIDC client application that verifies user identity. -This documentation applies to F5 NGINX Plus R15 and later. -
+{{< note >}} This guide applies to [NGINX Plus Release 34]({{< ref "nginx/releases.md#r34" >}}) and later. In earlier versions, NGINX Plus relied on an [njs-based solution](#legacy-njs-guide), which required NGINX JavaScript files, key-value stores, and advanced OpenID Connect logic. In the latest NGINX Plus version, the new [OpenID Connect module](https://nginx.org/en/docs/http/ngx_http_oidc_module.html) simplifies this process to just a few directives.{{< /note >}} -You can use NGINX Plus with [Auth0](https://auth0.com/) and OpenID Connect to enable single sign-on (SSO) for your proxied applications. By following the steps in this guide, you will learn how to set up SSO using OpenID Connect as the authentication mechanism, with Auth0 as the identity provider (IdP), and NGINX Plus as the relying party. - -{{< see-also >}}{{< include "nginx-plus/nginx-openid-repo-note.txt" >}}{{< /see-also >}} ## Prerequisites -To complete the steps in this guide, you need the following: +- An [Auth0](https://auth0.com/) tenant with administrator privileges. -- An Auth0 tenant with administrator privileges. -- [NGINX Plus](https://www.f5.com/products/nginx/nginx-plus) with a valid subscription. -- The [NGINX JavaScript module](https://www.nginx.com/products/nginx/modules/nginx-javascript/) (`njs`) -- the `njs` module handles the interaction between NGINX Plus and Auth0. +- An NGINX Plus [subscription](https://www.f5.com/products/nginx/nginx-plus) and NGINX Plus [Release 34](({{< ref "nginx/releases.md#r34" >}})) or later. For installation instructions, see [Installing NGINX Plus](https://docs.nginx.com/nginx/admin-guide/installing-nginx/installing-nginx-plus/). -## Install NGINX Plus and the njs Module {#install-nginx-plus-njs} +- A domain name pointing to your NGINX Plus instance, for example, `demo.example.com`. -1. If you do not already have NGINX Plus installed, follow the steps in the [NGINX Plus Admin Guide](https://docs.nginx.com/nginx/admin-guide/installing-nginx/installing-nginx-plus/) to do so. -2. Install the NGINX JavaScript module by following the steps in the [`njs` installation guide](https://nginx.org/en/docs/njs/install.html). -3. Add the following directive to the top-level ("main") configuration context in the NGINX Plus configuration (`/etc/nginx/nginx.conf`) to load the `njs` module: - ```Nginx configuration file - load_module modules/ngx_http_js_module.so; - ``` +## Create a new Auth0 Application {#auth0-create} -## Configure Auth0 {#config-auth0} +1. Log in to your Auth0 Dashboard at [manage.auth0.com](https://manage.auth0.com/). -Take the steps in this section to create a new application for NGINX Plus. +2. Select **Applications > Applications** from the sidebar menu. -{{< note >}} This section contains images that reflect the state of the Auth0 web interface at the time of publication. The actual Auth0 GUI may differ from the examples shown here. Use this guide as a reference and adapt the instructions to suit the current Auth0 GUI as necessary.{{< /note >}} +3. On the **Applications** screen, select **Create Application**. -### Create a new Auth0 Application {#create-auth0-app} +4. On the **Create application** screen: -1. Log in to your Auth0 Dashboard at [manage.auth0.com](https://manage.auth0.com/). -1. Select **Applications > Applications** from the sidebar menu. -1. On the **Applications** page, select the **Create Application** button. -1. In the **Create application** window, provide the information listed below and then select **Create**. + - Enter the **Name** for the application, for example, **Nginx Demo App**. - - **Name**: A name for the application, for example "nginx-plus-app". - - **Application Type**: **Regular Web Applications** + - In **Application Type**, select **Regular Web Applications**. - {{< img src="/img/sso/auth0/sso-auth0-create-app.png" alt="image showing the Create application window in the Auth0 dashboard" >}} + - Select **Create**. -### Set up the Web Application {#web-app-setup} +5. On the **Settings** screen of your application: -In this section, you'll set up a web application that follows the Auth0 [Authorization Code Flow](https://auth0.com/docs/get-started/authentication-and-authorization-flow/authorization-code-flow). + - Copy your **Client ID** and **Client Secret** displayed in the **Basic Information** section — you will need them later when configuring NGINX Plus. -1. On the **Application** page in the [Auth0 dashboard](https://manage.auth0.com/), select your web application. -1. Select the **Settings** tab for your application. -1. Make note of the Client ID and Client Secret displayed in the **Basic Information** section. +6. On the **Application URIs** section of your application: - {{< img src="/img/sso/auth0/sso-auth0-app.png" alt="image showing the basic information section of the web application settings in the Auth0 dashboard" >}} + - Add the URI NGINX Plus callback URI in the **Allowed Callback URLs** field, for example: -1. In the **Application URIs** section, provide the URI of the NGINX Plus instance in the **Allowed Callback URLs** field. + `https://demo.example.com/oidc_callback`. - - The URL must include the port number and end in **/_codexch**. In our example, we used the URL `http://nginx-plus-app:8010/_codexch`. - - The port is always required, even if you use the default port for HTTP (`80`) or HTTPS (`443`). - - The use of SSL/TLS (`443`) is strongly recommended for production environments. +### Get the OpenID Connect Discovery URL - {{< img src="/img/sso/auth0/sso-auth0-app-settings.png" alt="image showing the Application URIs settings in the Auth0 dashboard" >}} +Check the OpenID Connect Discovery URL. By default, Auth0 publishes the `.well-known/openid-configuration` document at the following address: -1. In the **Advanced Settings** section, select the **Endpoints** tab. -1. Make note of the **OpenID Configuration** URL. +`https://yourTenantId.us.auth0.com/.well-known/openid-configuration`. - {{< img src="/img/sso/auth0/sso-auth0-app-advanced-settings.png" alt="image showing the Advanced Application Settings section of the Auth0 dashboard" >}} +1. Run the following `curl` command in a terminal: -1. Select **Save Changes**. + ```shell + curl https://yourTenantId.us.auth0.com/.well-known/openid-configuration | jq + ``` + where: -### Set up Authentication {#authn-setup} + - the `yourTenantId` is your Auth0 [Tenant ID](https://auth0.com/docs/get-started/tenant-settings/find-your-tenant-name-or-tenant-id) -{{< note >}}For the purposes of this guide, we will add a new Auth0 user database and user account to use for testing. + - the `yourTenantId.us.auth0.com/` is your Auth0 server address -You can set up authentication using any of the available [Auth0 identity providers](https://auth0.com/docs/authenticate/identity-providers). {{< /note >}} + - the `/.well-known/openid-configuration` is the default address for Auth0 for document location -To set up a new user database and add a user account to it, take the steps below. + - the `jq` command (optional) is used to format the JSON output for easier reading and requires the [jq](https://jqlang.github.io/jq/) JSON processor to be installed. -1. Log in to the [Auth0 dashboard](https://manage.auth0.com/) and select **Authentication > Database** from the sidebar menu. -1. Select the **Create DB Connection** button. -1. Provide a **Name** for the database connection, then select **Create**. -1. On the **Database** page, select the **Applications** tab. Then, select the toggle button next to the [application you created earlier](#create-a-new-auth0-application). - {{< img src="/img/sso/auth0/sso-auth0-db-app.png" alt="image showing the Applications settings for an OIDC Authentication database in the Auth0 dashboard" >}} + The configuration metadata is returned in the JSON format: -1. In the sidebar menu, select **User Management > Users**. -1. On the **Users** page, select the **Create User** button. -1. In the **Create user** window, provide the following information, then select **Create**. - - **Email**: user's email - - **Password**: a password for the user account - - **Connection**: select your **database** from the list. + ```json + { + ... + "issuer": "https://{yourTenantId}.us.auth0.com/", + "authorization_endpoint": "https://{yourTenantId}.us.auth0.com/oauth/token", + "token_endpoint": "https://{yourTenantId}.us.auth0.com/oauth/token", + "jwks_uri": "https://{yourTenantId}.us.auth0.com/.well-known/jwks.json", + ... + } + ``` - {{< img src="/img/sso/auth0/sso-auth0-create-user.png" alt="image showing the Create User settings window in the Auth0 dashboard" >}} + +2. Copy the **issuer** value, you will need it later when configuring NGINX Plus. Typically, the OpenID Connect Issuer for Auth0 is `https://yourTenantId.us.auth0.com/` (including the trailing slash). To verify the accuracy of the endpoints, refer to the [Auth0 official documentation](https://auth0.com/docs/get-started/applications/configure-applications-with-oidc-discovery). + +{{< note >}} You will need the values of **Client ID**, **Client Secret**, and **Issuer** in the next steps. {{< /note >}} -The user should receive an email to the email address provided. Once the user verifies their account by clicking on the link in the email, the account creation process is complete. ## Set up NGINX Plus {#nginx-plus-setup} -Take the steps in this section to set up NGINX Plus as the OpenID Connect relying party. +With Auth0 configured, you can enable OIDC on NGINX Plus. NGINX Plus serves as the Rely Party (RP) application — a client service that verifies user identity. + +1. Ensure that you are using the latest version of NGINX Plus by running the `nginx -v` command in a terminal: -### Configure NGINX OpenID Connect {#nginx-plus-oidc-config} + ```shell + nginx -v + ``` + The output should match NGINX Plus Release 34 or later: -1. Clone the [nginx-openid-connect](https://github.com/nginxinc/nginx-openid-connect) GitHub repository, or download the repo files. + ```none + nginx version: nginx/1.27.4 (nginx-plus-r34) + ``` - ```bash - git clone https://github.com/nginxinc/nginx-openid-connect.git - ``` +2. Ensure that you have the values of the **Client ID**, **Client Secret**, and **Issuer** obtained during [Auth0 Configuration](#auth0-setup). + +3. In your preferred text editor, open the NGINX configuration file (`/etc/nginx/nginx.conf` for Linux or `/usr/local/etc/nginx/nginx.conf` for FreeBSD). + +4. In the [`http {}`](https://nginx.org/en/docs/http/ngx_http_core_module.html#http) context, make sure your public DNS resolver is specified with the [`resolver`](https://nginx.org/en/docs/http/ngx_http_core_module.html#resolver) directive: By default, NGINX Plus re‑resolves DNS records at the frequency specified by time‑to‑live (TTL) in the record, but you can override the TTL value with the `valid` parameter: + + ```nginx + http { + resolver 10.0.0.1 ipv4=on valid=300s; + + # ... + } + ``` -1. Run the *configure.sh* script, which will update the NGINX configuration files with the values for your Auth0 application. + +5. In the [`http {}`](https://nginx.org/en/docs/http/ngx_http_core_module.html#http) context, define the Auth0 provider named `auth0` by specifying the [`oidc_provider {}`](https://nginx.org/en/docs/http/ngx_http_oidc_module.html#oidc_provider) context: - For example: + ```nginx + http { + resolver 10.0.0.1 ipv4=on valid=300s; - ```bash - ./nginx-openid-connect/configure.sh \ - --auth_jwt_key request \ - --client_id Nhotzxx...IERmUi \ - --client_secret 6ZHd0j_r...UtDZ5bkdu \ - https://.us.auth0.com/.well-known/openid-configuration + oidc_provider auth0 { + + # ... + + } + # ... + } ``` -1. In the `frontend.conf` file, update the **my_backend** upstream with the address of the application that you want to add OIDC authorization to. +6. In the [`oidc_provider {}`](https://nginx.org/en/docs/http/ngx_http_oidc_module.html#oidc_provider) context, specify: + + - your actual Auth0 **Client ID** obtained in [Auth0 Configuration](#auth0-create) with the [`client_id`](https://nginx.org/en/docs/http/ngx_http_oidc_module.html#client_id) directive + + - your **Client Secret** obtained in [Auth0 Configuration](#auth0-create) with the [`client_secret`](https://nginx.org/en/docs/http/ngx_http_oidc_module.html#client_secret) directive + + - the **Issuer** URL obtained in [Auth0 Configuration](#auth0-create) with the [`issuer`](https://nginx.org/en/docs/http/ngx_http_oidc_module.html#client_secret) directive + + The `issuer` is typically your Auth0 OIDC URL. For Auth0, a trailing slash is included, for example: `https://yourTenantId.us.auth0.com/`. + + - **Important:** All interaction with the IdP is secured exclusively over SSL/TLS, so NGINX must trust the certificate presented by the IdP. By default, this trust is validated against your system’s CA bundle (the default CA store for your Linux or FreeBSD distribution). If the IdP’s certificate is not included in the system CA bundle, you can explicitly specify a trusted certificate or chain with the [`ssl_trusted_certificate`](https://nginx.org/en/docs/http/ngx_http_oidc_module.html#ssl_trusted_certificate) directive so that NGINX can validate and trust the IdP’s certificate. + - For example: + ```nginx + http { + resolver 10.0.0.1 ipv4=on valid=300s; - ```Nginx configuration file - upstream my_backend { - zone my_backend 64k; - server my-backend-app.com:80; + oidc_provider auth0 { + issuer https://yourTenantId.us.auth0.com/; + client_id ; + client_secret ; + } + + # ... } ``` -1. In the *openid_connect.server_conf* file, add the [`proxy_set_header`](https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_set_header) directive to the `/_jwks_uri` and `/_token` locations to `Accept-Encoding "gzip"`, as shown below. +7. Make sure you have configured a [server](https://nginx.org/en/docs/http/ngx_http_core_module.html#server) that corresponds to `demo.example.com`, and there is a [location](https://nginx.org/en/docs/http/ngx_http_core_module.html#location) that [points](https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_pass) to your application (see [Step 10](#oidc_app)) at `http://127.0.0.1:8080` that is going to be OIDC-protected: + + ```nginx + http { + + # ... + + server { + listen 443 ssl; + server_name demo.example.com; + + ssl_certificate /etc/ssl/certs/fullchain.pem; + ssl_certificate_key /etc/ssl/private/key.pem; - ```Nginx configuration file - ... - location = /_jwks_uri { - ... - proxy_set_header Accept-Encoding "gzip" + location / { + + # ... + + proxy_pass http://127.0.0.1:8080; + } + } + # ... } - ... - location = /_token { - ... - proxy_set_header Accept-Encoding "gzip" - } - ... ``` -1. Copy the following files to the */etc/nginx/conf.d* directory on the host machine where NGINX Plus is installed: +8. Protect this [location](https://nginx.org/en/docs/http/ngx_http_core_module.html#location) with Auth0 OIDC by specifying the [`auth_oidc`](https://nginx.org/en/docs/http/ngx_http_oidc_module.html#auth_oidc) directive that will point to the `auth0` configuration specified in the [`oidc_provider {}`](https://nginx.org/en/docs/http/ngx_http_oidc_module.html#oidc_provider) context in [Step 5](#auth0-setup-oidc-provider): - - `frontend.conf` - - `openid_connect.js` - - `openid_connect.server_conf` - - `openid_connect_configuration.conf` + ```nginx + # ... + location / { + auth_oidc auth0; -1. Reload the NGINX configuration: + # ... - ```bash - sudo nginx -s reload + proxy_pass http://127.0.0.1:8080; + } + # ... ``` -## Test the Setup +9. Pass the OIDC claims as headers to the application ([Step 10](#oidc_app)) with the [`proxy_set_header`](https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_set_header) directive. These claims are extracted from the ID token returned by Auth0: + + - [`$oidc_claim_email`](https://nginx.org/en/docs/http/ngx_http_oidc_module.html#var_oidc_claim_) the e-mail address of the user + + - [`$oidc_claim_name`](https://nginx.org/en/docs/http/ngx_http_oidc_module.html#var_oidc_claim_) - the full name of the user + + - any other OIDC claim using the [`$oidc_claim_ `](https://nginx.org/en/docs/http/ngx_http_oidc_module.html#var_oidc_claim_) variable + + ```nginx + # ... + location / { + auth_oidc auth0; + + proxy_set_header sub $oidc_claim_sub; + proxy_set_header email $oidc_claim_email; + proxy_set_header name $oidc_claim_name; + + proxy_pass http://127.0.0.1:8080; + } + # ... + ``` + + +10. Create a simple test application referenced by the `proxy_pass` directive which returns the authenticated user's full name and email upon successful authentication: + + ```nginx + # ... + server { + listen 8080; + + location / { + return 200 "Hello, $http_name!\nEmail: $http_email\nAuth0 sub: $http_sub\n"; + default_type text/plain; + } + } + ``` +11. Save the NGINX configuration file and reload the configuration: + ```nginx + nginx -s reload + ``` + +### Complete Example + +This configuration example summarizes the steps outlined above. It includes only essential settings such as specifying the DNS resolver, defining the OIDC provider, configuring SSL, and proxying requests to an internal server. + +```nginx +http { + # Use a public DNS resolver for Issuer discovery, etc. + resolver 10.0.0.1 ipv4=on valid=300s; + + oidc_provider auth0 { + # Issuer from your Auth0 tenant's .well-known/openid-configuration + issuer https://yourTenantId.us.auth0.com/; + + # Replace with your actual Client ID and Secret from Auth0 + client_id ; + client_secret ; + } + + server { + listen 443 ssl; + server_name demo.example.com; + + ssl_certificate /etc/ssl/certs/fullchain.pem; + ssl_certificate_key /etc/ssl/private/key.pem; + + location / { + # Enforce OIDC for root path with Auth0 + auth_oidc auth0; + + # Forward OIDC claims to the upstream as headers if desired + proxy_set_header email $oidc_claim_email; + proxy_set_header name $oidc_claim_name; + + proxy_pass http://127.0.0.1:8080; + } + } + + server { + # Simple test upstream server + listen 8080; + + location / { + return 200 "Hello, $http_name!\nEmail: $http_email\nAuth0 sub: $http_sub\n"; + default_type text/plain; + } + } +} +``` + +### Testing + +1. Open `https://demo.example.com/` in a browser. You will be redirected to the Auth0 sign-in page. + +2. Enter valid Auth0 credentials of a user who has access the application. Upon successful sign-in, Auth0 redirects you back to NGINX Plus, and you will see the proxied application content (for example, “Hello, Jane Doe!”). + + +## Legacy njs-based Auth0 Solution {#legacy-njs-guide} + +If you are running NGINX Plus R33 and earlier or if you still need the njs-based solution, refer to the [Legacy njs-based Auth0 Guide]({{< ref "nginx/deployment-guides/single-sign-on/oidc-njs/auth0.md" >}}) for details. The solution uses the [`nginx-openid-connect`](https://github.com/nginxinc/nginx-openid-connect) GitHub repository and NGINX JavaScript files. -1. In a browser, enter the address of your NGINX Plus instance. You should be directed to the Auth0 login page, as shown in the example below. - {{< img src="/img/sso/auth0/sso-auth0-login-test.png" alt="image showing an example Auth0 login screen that contains username and password fields" >}} +## See Also -1. You should be able to log in using the credentials of the user account that you created in the Auth0 database. +- [NGINX Plus Native OIDC Module Reference documentation](https://nginx.org/en/docs/http/ngx_http_oidc_module.html) -## Troubleshooting +- [Release Notes for NGINX Plus R34]({{< ref "nginx/releases.md#r34" >}}) -Refer to the [Troubleshooting](https://github.com/nginxinc/nginx-openid-connect#troubleshooting) section in the `nginx-openid-connect` repository on GitHub. ## Revision History -- Version 1 (May 2022) - Initial version +- Version 1 (March 2025) – Initial version (NGINX Plus Release 34) diff --git a/content/nginx/deployment-guides/single-sign-on/cognito.md b/content/nginx/deployment-guides/single-sign-on/cognito.md index ff5f182fb..272592132 100644 --- a/content/nginx/deployment-guides/single-sign-on/cognito.md +++ b/content/nginx/deployment-guides/single-sign-on/cognito.md @@ -1,189 +1,283 @@ --- -description: Enable OpenID Connect-based single-sign for applications proxied by NGINX - Plus, using Amazon Cognito as the identity provider (IdP). -docs: DOCS-464 +description: Enable OpenID Connect-based single sign-on (SSO) for applications proxied by NGINX Plus, using Amazon Cognito as the identity provider (IdP). +doctypes: +- task title: Single Sign-On with Amazon Cognito toc: true -weight: 100 -type: -- how-to +weight: 200 --- -This guide explains how to enable single sign‑on (SSO) for applications being proxied by F5 NGINX Plus. The solution uses OpenID Connect as the authentication mechanism, with [Amazon Cognito](https://aws.amazon.com/cognito/) as the identity provider (IdP), and NGINX Plus as the relying party. +This guide explains how to enable single sign-on (SSO) for applications being proxied by F5 NGINX Plus. The solution uses OpenID Connect as the authentication mechanism, with [Amazon Cognito](https://aws.amazon.com/cognito/) as the Identity Provider (IdP), and NGINX Plus as the Relying Party, or OIDC client application that verifies user identity. -{{< see-also >}}{{< include "nginx-plus/nginx-openid-repo-note.txt" >}}{{< /see-also >}} +{{< note >}} This guide applies to [NGINX Plus Release 34]({{< ref "nginx/releases.md#r34" >}}) and later. In earlier versions, NGINX Plus relied on an [njs-based solution](#legacy-njs-guide), which required NGINX JavaScript files, key-value stores, and advanced OpenID Connect logic. In the latest NGINX Plus version, the new [OpenID Connect module](https://nginx.org/en/docs/http/ngx_http_oidc_module.html) simplifies this process to just a few directives.{{< /note >}} - ## Prerequisites -The instructions assume you have the following: +- An [AWS account](https://aws.amazon.com/premiumsupport/knowledge-center/create-and-activate-aws-account/) -- An [AWS account](https://aws.amazon.com/premiumsupport/knowledge-center/create-and-activate-aws-account/). -- An NGINX Plus subscription and NGINX Plus R15 or later. For installation instructions, see the [NGINX Plus Admin Guide](https://docs.nginx.com/nginx/admin-guide/installing-nginx/installing-nginx-plus/). -- The [NGINX JavaScript module](https://www.nginx.com/blog/introduction-nginscript/) (njs), required for handling the interaction between NGINX Plus and the IdP. After installing NGINX Plus, install the module with the command for your operating system. +- A Cognito **User Pool** + +- An NGINX Plus [subscription](https://www.f5.com/products/nginx/nginx-plus) and NGINX Plus [Release 34](({{< ref "nginx/releases.md#r34" >}})) or later. For installation instructions, see [Installing NGINX Plus](https://docs.nginx.com/nginx/admin-guide/installing-nginx/installing-nginx-plus/). - For Debian and Ubuntu: +- A domain name pointing to your NGINX Plus instance, for example, `demo.example.com`. - ```none - sudo apt install nginx-plus-module-njs - ``` - For CentOS, RHEL, and Oracle Linux: +## Configure Amazon Cognito {#cognito-setup} - ```shell - sudo yum install nginx-plus-module-njs - ``` +1. Open the Amazon Cognito console in the AWS Management Console -- The following directive included in the top-level ("main") configuration context in **/etc/nginx/nginx.conf**, to load the NGINX JavaScript module: +2. In the Cognito dashboard, select **Create or open a User Pool**. - ```nginx - load_module modules/ngx_http_js_module.so; - ``` +3. **Create an App Client** (for example, “nginx-demo-app”) and **enable the “Generate client secret”** option. - -## Configuring Amazon Cognito +4. In the App client settings, select **Enable Cognito User Pool** as an **Identity Provider**: -**Note:** The following procedure reflects the Cognito GUI at the time of publication, but the GUI is subject to change. Use this guide as a reference and adapt to the current Cognito GUI as necessary. + - Add a **Callback URL**: `https://demo.example.com/oidc_callback`. -Create a new application for NGINX Plus in the Cognito GUI: + - Enable **Authorization code grant**. -1. Log in to your AWS account, open the AWS Management Console ([console.aws.amazon.com](https://console.aws.amazon.com)), and navigate to the Cognito dashboard (you can, for example, click **Cognito** in the **Security, Identity, & Compliance** section of the **Services** drop‑down menu). + - In the **OAuth scopes**, check the values of **openid**, **profile**, and **email**. -2. On the Cognito dashboard, click **Manage User Pools** to open the **Your User Pools** window. Click the  Create a user pool  button or the highlighted phrase. +5. Copy the following values — you will need them later when configuring NGINX Plus. - + - **User Pool ID**, for example, `us-east-2_abCdEfGhI` -3. In the **Create a user pool** window that opens, type a value in the **Pool name** field (in this guide, it's nginx-plus-pool), then click the Review defaults button. + - **App client id** and **App client secret** - + - **AWS region**, for example, `us-east-2` - -4. On the **Review** tab which opens, click Add app client... in the **App clients** field near the bottom. + - **Issuer**, for example, `https://cognito-idp..amazonaws.com/` - +{{< note >}} You will need the values of **Client ID**, **Client Secret**, and **Issuer** in the next steps. {{< /note >}} -5. On the **App clients** tab which opens, click Add an app client. -6. On the **Which app clients will have access to this user pool?** window which opens, enter a value (in this guide, nginx-plus-app) in the App client name field. Make sure the Generate client secret box is checked, then click the  Create app client  button. +## Set up NGINX Plus {#nginx-plus-setup} - +With Cognito configured, you can enable OIDC on NGINX Plus. NGINX Plus serves as the Rely Party (RP) application — a client service that verifies user identity. -7. On the confirmation page which opens, click Return to pool details to return to the **Review** tab. On that tab click the  Create pool  button at the bottom. (The screenshot in [Step 4](#cognito-review-tab) shows the button.) +1. Ensure that you are using the latest version of NGINX Plus by running the `nginx -v` command in a terminal: - -8. On the details page which opens to confirm the new user pool was successfully created, make note of the value in the **Pool Id** field; you will add it to the NGINX Plus configuration in [Step 3 of _Configuring NGINX Plus_](#nginx-plus-variables). + ```shell + nginx -v + ``` + The output should match NGINX Plus Release 34 or later: - 'General settings' tab in Amazon Cognito GUI + ```none + nginx version: nginx/1.27.4 (nginx-plus-r34) + ``` - -9. Click Users and groups in the left navigation column. In the interface that opens, designate the users (or group of users, on the **Groups** tab) who will be able to use SSO for the app being proxied by NGINX Plus. For instructions, see the Cognito documentation about [creating users](https://docs.aws.amazon.com/cognito/latest/developerguide/how-to-create-user-accounts.html), [importing users](https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pools-using-import-tool.html), or [adding a group](https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pools-user-groups.html). +2. Ensure that you have the values of the **Client ID**, **Client Secret**, and **Issuer** obtained during [Cognito Configuration](#cognito-setup). - 'Users and groups' tab in Amazon Cognito GUI +3. In your preferred text editor, open the NGINX configuration file (`/etc/nginx/nginx.conf` for Linux or `/usr/local/etc/nginx/nginx.conf` for FreeBSD). -10. Click **App clients** in the left navigation bar. On the tab that opens, click the Show Details button in the box labeled with the app client name (in this guide, nginx-plus-app). +4. In the [`http {}`](https://nginx.org/en/docs/http/ngx_http_core_module.html#http) context, make sure your public DNS resolver is specified with the [`resolver`](https://nginx.org/en/docs/http/ngx_http_core_module.html#resolver) directive: By default, NGINX Plus re‑resolves DNS records at the frequency specified by time‑to‑live (TTL) in the record, but you can override the TTL value with the `valid` parameter: - 'App clients' tab in Amazon Cognito GUI + ```nginx + http { + resolver 10.0.0.1 ipv4=on valid=300s; - -11. On the details page that opens, make note of the values in the App client id and App client secret fields. You will add them to the NGINX Plus configuration in [Step 3 of _Configuring NGINX Plus_](#nginx-plus-variables). + # ... + } + ``` - + +5. In the [`http {}`](https://nginx.org/en/docs/http/ngx_http_core_module.html#http) context, define the Amazon Cognito provider named `cognito` by specifying the [`oidc_provider {}`](https://nginx.org/en/docs/http/ngx_http_oidc_module.html#oidc_provider) context: -12. Click App client settings in the left navigation column. In the tab that opens, perform the following steps: + ```nginx + http { + resolver 10.0.0.1 ipv4=on valid=300s; - 1. In the Enabled Identity Providers section, click the Cognito User Pool checkbox (the **Select all** box gets checked automatically). - 2. In the **Callback URL(s)** field of the Sign in and sign out URLs section, type the URI of the NGINX Plus instance including the port number, and ending in **/\_codexch**. Here we’re using https://my-nginx-plus.example.com:443/_codexch. + oidc_provider cognito { - **Notes:** + # ... - - For production, we strongly recommend that you use SSL/TLS (port 443). - - The port number is mandatory even when you're using the default port for HTTP (80) or HTTPS (443). + } + # ... + } + ``` - 3. In the **OAuth 2.0** section, click the Authorization code grant checkbox under Allowed OAuth Flows and the **email**, **openid**, and **profile** checkboxes under Allowed OAuth Scopes. - 4. Click the  Save changes  button. +6. In the [`oidc_provider {}`](https://nginx.org/en/docs/http/ngx_http_oidc_module.html#oidc_provider) context, specify: - + - your actual Amazon Cognito **Client ID** obtained in [Amazon Cognito Configuration](#cognito-setup) with the [`client_id`](https://nginx.org/en/docs/http/ngx_http_oidc_module.html#client_id) directive - -13. Click **Domain name** in the left navigation column. In the tab that opens, type a domain prefix in the **Domain prefix** field under Amazon Cognito domain (in this guide, my-nginx-plus). Click the  Save changes  button. + - your **Client Secret** obtained in [Amazon Cognito Configuration](#cognito-setup) with the [`client_secret`](https://nginx.org/en/docs/http/ngx_http_oidc_module.html#client_secret) directive - + - the **Issuer** URL obtained in [Amazon Cognito Configuration](#cognito-setup) with the [`issuer`](https://nginx.org/en/docs/http/ngx_http_oidc_module.html#client_secret) directive - -## Configuring NGINX Plus + The `issuer` is typically your Amazon Cognito OIDC URL. As a rule, Cognito uses a unique issuer for each User Pool, for example: -Configure NGINX Plus as the OpenID Connect relying party: + `https://cognito-idp.us-east-2.amazonaws.com/us-east-2_abCdEfGhI` -1. Create a clone of the [nginx-openid-connect](https://github.com/nginxinc/nginx-openid-connect) GitHub repository. + By default, NGINX Plus creates the metadata URL by appending the `/.well-known/openid-configuration` part to the Issuer URL. If your Issuer is different, you can explicitly specify the metadata document with the [`config_url`](https://nginx.org/en/docs/http/ngx_http_oidc_module.html#config_url) directive. - ```shell - git clone https://github.com/nginxinc/nginx-openid-connect - ``` + - **Important:** All interaction with the IdP is secured exclusively over SSL/TLS, so NGINX must trust the certificate presented by the IdP. By default, this trust is validated against your system’s CA bundle (the default CA store for your Linux or FreeBSD distribution). If the IdP’s certificate is not included in the system CA bundle, you can explicitly specify a trusted certificate or chain with the [`ssl_trusted_certificate`](https://nginx.org/en/docs/http/ngx_http_oidc_module.html#ssl_trusted_certificate) directive so that NGINX can validate and trust the IdP’s certificate. -2. Copy these files from the clone to **/etc/nginx/conf.d**: + ```nginx + http { + resolver 10.0.0.1 ipv4=on valid=300s; - - **frontend.conf** - - **openid_connect.js** - - **openid_connect.server\_conf** + oidc_provider cognito { + issuer https://cognito-idp.us-east-2.amazonaws.com/us-east-2_abCdEfGhI; + client_id ; + client_secret ; + } - -3. In your preferred text editor, open **/etc/nginx/conf.d/frontend.conf**. Change the second parameter of each of the following [set](http://nginx.org/en/docs/http/ngx_http_rewrite_module.html#set) directives to the specified value. + # ... + } + ``` - The `` variable is the full value in the **Domain prefix** field in [Step 13 of _Configuring Amazon Cognito_](#cognito-domain-name). In this guide it is https://my-nginx-plus.auth.us-east-2.amazoncognito.com. +7. Make sure you have configured a [server](https://nginx.org/en/docs/http/ngx_http_core_module.html#server) that corresponds to `demo.example.com`, and there is a [location](https://nginx.org/en/docs/http/ngx_http_core_module.html#location) that [points](https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_pass) to your application (see [Step 10](#oidc_app)) at `http://127.0.0.1:8080` that is going to be OIDC-protected: - - `set $oidc_authz_endpoint` – `/oauth2/authorize` - - `set $oidc_token_endpoint` – `/oauth2/token` - - `set $oidc_client` – Value in the App client id field from [Step 11 of _Configuring Amazon Cognito_](#cognito-app-client-id-secret) - - `set $oidc_client_secret` – Value in the App client secret field from [Step 11 of _Configuring Amazon Cognito_](#cognito-app-client-id-secret) - - `set $oidc_hmac_key` – A unique, long, and secure phrase + ```nginx + http { -4. Configure the JWK file. The file's URL is + # ... - **https://cognito-idp.**_region_**.amazonaws.com/**_User-Pool-ID_**/.well-known/jwks.json** + server { + listen 443 ssl; + server_name demo.example.com; - where + ssl_certificate /etc/ssl/certs/fullchain.pem; + ssl_certificate_key /etc/ssl/private/key.pem; - - _region_ is the same AWS region name as in the `` variable used in [Step 3](#nginx-plus-variables) - - _User-Pool-ID_ is the value in the **Pool Id** field in [Step 8 of _Configuring Amazon Cognito_](#cognito-pool-id) + location / { - In this guide, the URL is + # ... - https://cognito-idp.us-east-2.amazonaws.com/us-east-2_mLoGHJpOs/.well-known/jwks.json. + proxy_pass http://127.0.0.1:8080; + } + } + # ... + } + ``` - The method for configuring the JWK file depends on which version of NGINX Plus you are using: +8. Protect this [location](https://nginx.org/en/docs/http/ngx_http_core_module.html#location) with Amazon Cognito OIDC by specifying the [`auth_oidc`](https://nginx.org/en/docs/http/ngx_http_oidc_module.html#auth_oidc) directive that will point to the `cognito` configuration specified in the [`oidc_provider {}`](https://nginx.org/en/docs/http/ngx_http_oidc_module.html#oidc_provider) context in [Step 5](#cognito-setup-oidc-provider): - - In NGINX Plus R17 and later, NGINX Plus can read the JWK file directly. Change **/etc/nginx/conf.d/frontend.conf** as follows: + ```nginx + # ... + location / { + auth_oidc cognito; - 1. Comment out (or remove) the [auth_jwt_key_file](http://nginx.org/en/docs/http/ngx_http_auth_jwt_module.html#auth_jwt_key_file) directive. - 2. Uncomment the [auth_jwt_key_request](http://nginx.org/en/docs/http/ngx_http_auth_jwt_module.html#auth_jwt_key_request) directive. (Its parameter, `/_jwks_uri`, refers to the value of the `$oidc_jwt_keyfile` variable, which you set in the next step.) - 3. Change the second parameter of the `set $oidc_jwt_keyfile` directive to the URL of the JWK file (`https://cognito-idp.../.well-known/jwks.json`). + # ... - - In NGINX Plus R16 and earlier, the JWK file must be on the local disk. (You can also use this method with NGINX Plus R17 and later if you wish.) + proxy_pass http://127.0.0.1:8080; + } + # ... + ``` - 1. Copy the JSON contents from the JWK file (****) to a local file (for example, **/etc/nginx/my\_cognito\_jwk.json**). - 2. In **/etc/nginx/conf.d/frontend.conf**, change the second parameter of the `set $oidc_jwt_keyfile` directive to the local file path. +9. Pass the OIDC claims as headers to the application ([Step 10](#oidc_app)) with the [`proxy_set_header`](https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_set_header) directive. These claims are extracted from the ID token returned by Amazon Cognito: -5. At the time of publication, Cognito does not support the OpenID **offline_access** scope. Open **/etc/nginx/conf.d/openid\_connect.server\_conf** in a text editor and remove `+offline_access` from the list of scopes on line 10, so that it looks like this: + - [`$oidc_claim_sub`](https://nginx.org/en/docs/http/ngx_http_oidc_module.html#var_oidc_claim_) - a unique `Subject` identifier assigned for each user by Amazon Cognito - ```nginx - return 302 "$oidc_authz_endpoint?response_type=code&scope=openid+profile+email&client_id=$oidc_clientaws...; - ``` + - [`$oidc_claim_email`](https://nginx.org/en/docs/http/ngx_http_oidc_module.html#var_oidc_claim_) the e-mail address of the user -6. Confirm that the user named by the [user](http://nginx.org/en/docs/ngx_core_module.html#user) directive in the NGINX Plus configuration (in **/etc/nginx/nginx.conf** by convention) has read permission on the JWK file. + - [`$oidc_claim_name`](https://nginx.org/en/docs/http/ngx_http_oidc_module.html#var_oidc_claim_) - the full name of the user - -## Testing + - any other OIDC claim using the [`$oidc_claim_ `](https://nginx.org/en/docs/http/ngx_http_oidc_module.html#var_oidc_claim_) variable -In a browser, enter the address of your NGINX Plus instance and try to log in using the credentials of a user assigned to the application (see [Step 9 in _Configuring Amazon Cognito_](#cognito-users)). The NGINX logo that appears in the screenshot was added on Cognito's **UI customization** tab (not shown in this guide). + ```nginx + # ... + location / { + auth_oidc cognito; - + proxy_set_header sub $oidc_claim_sub; + proxy_set_header email $oidc_claim_email; + proxy_set_header name $oidc_claim_name; - -## Troubleshooting + proxy_pass http://127.0.0.1:8080; + } + # ... + ``` -See the [**Troubleshooting**](https://github.com/nginxinc/nginx-openid-connect#troubleshooting) section at the nginx-openid-connect repository on GitHub. + +10. Create a simple test application referenced by the `proxy_pass` directive which returns the authenticated user's full name and email upon successful authentication: -### Revision History + ```nginx + # ... + server { + listen 8080; -- Version 1 (March 2020) – Initial version (NGINX Plus Release 20) + location / { + return 200 "Hello, $http_name!\nEmail: $http_email\nSub: $http_sub\n"; + default_type text/plain; + } + } + ``` +11. Save the NGINX configuration file and reload the configuration: + ```nginx + nginx -s reload + ``` + +### Complete Example + +This configuration example summarizes the steps outlined above. It includes only essential settings such as specifying the DNS resolver, defining the OIDC provider, configuring SSL, and proxying requests to an internal server. + +```nginx +http { + # Use a public DNS resolver for Issuer discovery, etc. + resolver 10.0.0.1 ipv4=on valid=300s; + + oidc_provider cognito { + # Typically your Cognito issuer is something like: + # https://cognito-idp..amazonaws.com/ + issuer https://cognito-idp.us-east-2.amazonaws.com/us-east-2_abCdEfGhI; + + # Your Cognito "App client id" and "App client secret" + client_id ; + client_secret ; + } + + server { + listen 443 ssl; + server_name demo.example.com; + + ssl_certificate /etc/ssl/certs/fullchain.pem; + ssl_certificate_key /etc/ssl/private/key.pem; + + location / { + # Protect this path with Cognito OIDC + auth_oidc cognito; + + # Forward OIDC claims as headers if desired + proxy_set_header sub $oidc_claim_sub; + proxy_set_header email $oidc_claim_email; + proxy_set_header name $oidc_claim_name; + + proxy_pass http://127.0.0.1:8080; + } + } + + server { + listen 8080; + + location / { + return 200 "Hello, $http_name!\nEmail: $http_email\nSub: $http_sub\n"; + default_type text/plain; + } + } +} +``` + +### Testing + +1. Open https://demo.example.com/ in a browser. You will be automatically redirected to Amazon Cognito login page for your realm. + +2. Enter valid Cognito user credentials (those in the assigned user pool). Upon successful sign-in, Cognito redirects you back to NGINX Plus, and you will see the proxied application content (for example, “Hello, Jane Doe!”). + + +## Legacy njs-based Amazon Cognito Solution {#legacy-njs-guide} + +If you are running NGINX Plus R33 and earlier or if you still need the njs-based solution, refer to the [Legacy njs-based Cognito Guide]({{< ref "nginx/deployment-guides/single-sign-on/oidc-njs/cognito.md" >}}) for details. The solution uses the [`nginx-openid-connect`](https://github.com/nginxinc/nginx-openid-connect) GitHub repository and NGINX JavaScript files. + + +## See Also + +- [NGINX Plus Native OIDC Module Reference documentation](https://nginx.org/en/docs/http/ngx_http_oidc_module.html) + +- [Release Notes for NGINX Plus R34]({{< ref "nginx/releases.md#r34" >}}) + + +## Revision History + +- Version 1 (March 2025) – Initial version (NGINX Plus Release 34) diff --git a/content/nginx/deployment-guides/single-sign-on/entra-id.md b/content/nginx/deployment-guides/single-sign-on/entra-id.md new file mode 100644 index 000000000..30056e47f --- /dev/null +++ b/content/nginx/deployment-guides/single-sign-on/entra-id.md @@ -0,0 +1,297 @@ +--- +description: Enable OpenID Connect-based single sign-on (SSO) for applications proxied by NGINX Plus, using Microsoft Entra ID (formerly Azure Active Directory) as the identity provider (IdP). +doctypes: +- task +title: Single Sign-On with Microsoft Entra ID +toc: true +weight: 400 +--- + +This guide explains how to enable single sign-on (SSO) for applications being proxied by F5 NGINX Plus. The solution uses OpenID Connect as the authentication mechanism, with [Microsoft Entra ID](https://www.microsoft.com/en-us/security/business/identity-access/microsoft-entra-id) as the Identity Provider (IdP), and NGINX Plus as the Relying Party, or OIDC client application that verifies user identity. + +{{< note >}} This guide applies to [NGINX Plus Release 34]({{< ref "nginx/releases.md#r34" >}}) and later. In earlier versions, NGINX Plus relied on an [njs-based solution](#legacy-njs-guide), which required NGINX JavaScript files, key-value stores, and advanced OpenID Connect logic. In the latest NGINX Plus version, the new [OpenID Connect module](https://nginx.org/en/docs/http/ngx_http_oidc_module.html) simplifies this process to just a few directives.{{< /note >}} + + +## Prerequisites + +- A Microsoft Entra tenant with admin access. + +- Azure CLI. For installation instructions, see [How to install the Azure CLI](https://learn.microsoft.com/en-us/cli/azure/install-azure-cli). + +- An NGINX Plus [subscription](https://www.f5.com/products/nginx/nginx-plus) and NGINX Plus [Release 34](({{< ref "nginx/releases.md#r34" >}})) or later. For installation instructions, see [Installing NGINX Plus](https://docs.nginx.com/nginx/admin-guide/installing-nginx/installing-nginx-plus/). + +- A domain name pointing to your NGINX Plus instance, for example, `demo.example.com`. + + +## Configure Entra ID {#entra-setup} + +Register a new application in Microsoft Entra ID that will represent NGINX Plus as an OIDC client. This is necessary to obtain unique identifiers and secrets for OIDC, as well as to specify where Azure should return tokens. Ensure you have access to the Azure Portal with Entra ID app administrator privileges. + +### Register new Azure Web Application + +1. Log in to Azure CLI: + + ```bash + az login + ``` + This command will open your default browser for authentication. + +2. Register a New Application. + + - Create a new application, for example, "Nginx Demo App", with NGINX callback URI `/oidc_callback`: + + ```bash + az ad app create --display-name "Nginx Demo App" --web-redirect-uris "https://demo.example.com/oidc_callback" + ``` + + - From the command output, copy the `appId` value which represents your **Client ID**. You will need it later when configuring NGINX Plus. + +3. Generate a new Client Secret. + + - Create a client secret for your application by running: + + ```bash + az ad app credential reset --id + ``` + + - Replace the `` with the value obtained in the previous step. + + - From the command output, copy the the `password` value which represents your **Client Secret**. You will need it later when configuring NGINX Plus. Make sure to securely save the generated client secret, as it will not be displayed again. + + - From the same command output, copy the the `tenant` value which represents your **Tenant ID**. You will need it later when configuring NGINX Plus. + +{{< note >}} You will need the values of **Client ID**, **Client Secret**, and **Tenant ID** in the next steps. {{< /note >}} + +## Set up NGINX Plus {#nginx-plus} + +With Microsoft Entra ID configured, you can enable OIDC on NGINX Plus. NGINX Plus serves as the Rely Party (RP) application — a client service that verifies user identity. + +1. Ensure that you are using the latest version of NGINX Plus by running the `nginx -v` command in a terminal: + + ```shell + nginx -v + ``` + The output should match NGINX Plus Release 34 or later: + + ```none + nginx version: nginx/1.27.4 (nginx-plus-r34) + ``` + +2. Ensure that you have the values of the **Client ID**, **Client Secret**, and **Tenant ID** obtained during [Microsoft Entra ID Configuration](#entra-setup). + +3. In your preferred text editor, open the NGINX configuration file (`/etc/nginx/nginx.conf` for Linux or `/usr/local/etc/nginx/nginx.conf` for FreeBSD). + +4. In the [`http {}`](https://nginx.org/en/docs/http/ngx_http_core_module.html#http) context, make sure your public DNS resolver is specified with the [`resolver`](https://nginx.org/en/docs/http/ngx_http_core_module.html#resolver) directive: By default, NGINX Plus re‑resolves DNS records at the frequency specified by time‑to‑live (TTL) in the record, but you can override the TTL value with the `valid` parameter: + + ```nginx + http { + resolver 10.0.0.1 ipv4=on valid=300s; + + # ... + } + ``` + + +5. In the [`http {}`](https://nginx.org/en/docs/http/ngx_http_core_module.html#http) context, define the Entra ID provider named `entra` by specifying the [`oidc_provider {}`](https://nginx.org/en/docs/http/ngx_http_oidc_module.html#oidc_provider) context: + + ```nginx + http { + resolver 10.0.0.1 ipv4=on valid=300s; + + oidc_provider entra { + + # ... + + } + # ... + } + ``` + +6. In the [`oidc_provider {}`](https://nginx.org/en/docs/http/ngx_http_oidc_module.html#oidc_provider) context, specify: + + - your **Client ID** obtained in [Entra ID Configuration](#entra-setup) with the [`client_id`](https://nginx.org/en/docs/http/ngx_http_oidc_module.html#client_id) directive + + - your **Client Secret** obtained in [Entra ID Configuration](#entra-setup) with the [`client_secret`](https://nginx.org/en/docs/http/ngx_http_oidc_module.html#client_secret) directive + + - the **Issuer** URL obtained in [Entra ID Configuration](#entra-setup) with the [`issuer`](https://nginx.org/en/docs/http/ngx_http_oidc_module.html#client_secret) directive + + The `issuer` is typically: + + `https://login.microsoftonline.com//v2.0`. + + By default, NGINX Plus creates the metadata URL by appending the `/.well-known/openid-configuration` part to the Issuer URL. If your metadata URL is different, you can explicitly specify it with the [`config_url`](https://nginx.org/en/docs/http/ngx_http_oidc_module.html#config_url) directive. + + - **Important:** All interaction with the IdP is secured exclusively over SSL/TLS, so NGINX must trust the certificate presented by the IdP. By default, this trust is validated against your system’s CA bundle (the default CA store for your Linux or FreeBSD distribution). If the IdP’s certificate is not included in the system CA bundle, you can explicitly specify a trusted certificate or chain with the [`ssl_trusted_certificate`](https://nginx.org/en/docs/http/ngx_http_oidc_module.html#ssl_trusted_certificate) directive so that NGINX can validate and trust the IdP’s certificate. + + ```nginx + http { + resolver 10.0.0.1 ipv4=on valid=300s; + + oidc_provider entra { + issuer https://login.microsoftonline.com//v2.0; + client_id ; + client_secret ; + + ssl_trusted_certificate /etc/ssl/certs/ca-certificates.crt; + } + + # ... + } + ``` + +7. Make sure you have configured a [server](https://nginx.org/en/docs/http/ngx_http_core_module.html#server) that corresponds to `demo.example.com`, and there is a [location](https://nginx.org/en/docs/http/ngx_http_core_module.html#location) that [points](https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_pass) to your application (see [Step 10](#oidc_app)) at `http://127.0.0.1:8080` that is going to be OIDC-protected: + + ```nginx + http { + + # ... + + server { + listen 443 ssl; + server_name demo.example.com; + + ssl_certificate /etc/ssl/certs/fullchain.pem; + ssl_certificate_key /etc/ssl/private/key.pem; + + location / { + + # ... + + proxy_pass http://127.0.0.1:8080; + } + } + # ... + } + ``` + +8. Protect this [location](https://nginx.org/en/docs/http/ngx_http_core_module.html#location) with Entra ID OIDC by specifying the [`auth_oidc`](https://nginx.org/en/docs/http/ngx_http_oidc_module.html#auth_oidc) directive that will point to the `entra` configuration specified in the [`oidc_provider {}`](https://nginx.org/en/docs/http/ngx_http_oidc_module.html#oidc_provider) context in [Step 5](#entra-setup-oidc-provider): + + ```nginx + # ... + location / { + + auth_oidc entra; + + # ... + + proxy_pass http://127.0.0.1:8080; + + } + # ... + ``` + +9. Pass the OIDC claims as headers to the application ([Step 10](#oidc_app)) with the [`proxy_set_header`](https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_set_header) directive. These claims are extracted from the ID token returned by Entra ID: + + - [`$oidc_claim_sub`](https://nginx.org/en/docs/http/ngx_http_oidc_module.html#var_oidc_claim_) - a unique `Subject` identifier assigned for each user by Entra ID + + - [`$oidc_claim_email`](https://nginx.org/en/docs/http/ngx_http_oidc_module.html#var_oidc_claim_) - the e-mail address of the user + + - [`$oidc_claim_name`](https://nginx.org/en/docs/http/ngx_http_oidc_module.html#var_oidc_claim_) - the full name of the user + + - any other OIDC claim using the [`$oidc_claim_ `](https://nginx.org/en/docs/http/ngx_http_oidc_module.html#var_oidc_claim_) variable + + + ```nginx + # ... + location / { + + auth_oidc entra; + + proxy_set_header sub $oidc_claim_sub; + proxy_set_header email $oidc_claim_email; + proxy_set_header name $oidc_claim_name; + + proxy_pass http://127.0.0.1:8080; + } + # ... + ``` + + +10. Create a simple test application referenced by the `proxy_pass` directive which returns the authenticated user's full name and email upon successful authentication: + + ```nginx + # ... + server { + listen 8080; + + location / { + return 200 "Hello, $http_name!\nEmail: $http_email\nSub: $http_sub\n"; + default_type text/plain; + } + } + ``` +11. Save the NGINX configuration file and reload the configuration: + ```nginx + nginx -s reload + ``` + +### Complete Example + +This configuration example summarizes the steps outlined above. It includes only essential settings such as specifying the DNS resolver, defining the OIDC provider, configuring SSL, and proxying requests to an internal server. + +```nginx +http { + # Use a public DNS resolver for Issuer discovery, etc. + resolver 10.0.0.1 ipv4=on valid=300s; + + oidc_provider entra { + # The issuer is typically something like: + # https://login.microsoftonline.com//v2.0 + issuer https://login.microsoftonline.com//v2.0; + + # Replace with your actual Entra client_id and client_secret + client_id ; + client_secret ; + } + + server { + listen 443 ssl; + server_name demo.example.com; + + ssl_certificate /etc/ssl/certs/fullchain.pem; + ssl_certificate_key /etc/ssl/private/key.pem; + + location / { + # Protect this location with Entra OIDC + auth_oidc entra; + + # Forward OIDC claims as headers if desired + proxy_set_header sub $oidc_claim_sub; + proxy_set_header email $oidc_claim_email; + proxy_set_header name $oidc_claim_name; + + proxy_pass http://127.0.0.1:8080; + } + } + + server { + listen 8080; + + location / { + return 200 "Hello, $http_username!\n Your email is $http_email\n Your unique id is $http_sub\n"; + default_type text/plain; + } + } +} +``` + +### Testing + +1. Open `https://demo.example.com/` in a browser. You will be automatically redirected to the Enra ID sign-in page. + +2. Enter valid Entra ID credentials of a user who has access the application. Upon successful sign-in, Entra ID redirects you back to NGINX Plus, and you will see the proxied application content (for example, “Hello, Jane Doe!”). + +{{}}If you restricted access to a group of users, be sure to select a user who has access to the application.{{}} + + +## See Also + +- [Microsoft identity platform documentation](https://learn.microsoft.com/en-us/entra/identity-platform/) + +- [NGINX Plus Native OIDC Module Reference documentation](https://nginx.org/en/docs/http/ngx_http_oidc_module.html) + +- [Release Notes for NGINX Plus R34]({{< ref "nginx/releases.md#r34" >}}) + +## Revision History + +- Version 1 (March 2025) – Initial version (NGINX Plus Release 34) diff --git a/content/nginx/deployment-guides/single-sign-on/keycloak.md b/content/nginx/deployment-guides/single-sign-on/keycloak.md index d3c2a84ac..6f4218f71 100644 --- a/content/nginx/deployment-guides/single-sign-on/keycloak.md +++ b/content/nginx/deployment-guides/single-sign-on/keycloak.md @@ -1,168 +1,297 @@ --- -description: Enable OpenID Connect-based single-sign for applications proxied by NGINX - Plus, using Keycloak as the identity provider (IdP). -docs: DOCS-465 +description: Enable OpenID Connect-based single sign-on (SSO) for applications proxied by NGINX Plus, using Keycloak as the identity provider (IdP). +doctypes: +- task title: Single Sign-On with Keycloak toc: true -weight: 100 -type: -- how-to +weight: 500 --- -This guide explains how to enable single sign-on (SSO) for applications being proxied by F5 NGINX Plus. The solution uses OpenID Connect as the authentication mechanism, with [Keycloak](https://www.keycloak.org/) as the identity provider (IdP), and NGINX Plus as the relying party. +This guide explains how to enable single sign-on (SSO) for applications being proxied by F5 NGINX Plus. The solution uses OpenID Connect as the authentication mechanism, with [Keycloak](https://www.keycloak.org/) as the Identity Provider (IdP), and NGINX Plus as the Relying Party, or OIDC client application that verifies user identity. -{{< see-also >}}{{< include "nginx-plus/nginx-openid-repo-note.txt" >}}{{< /see-also >}} +{{< note >}} This guide applies to [NGINX Plus Release 34]({{< ref "nginx/releases.md#r34" >}}) and later. In earlier versions, NGINX Plus relied on an [njs-based solution](#legacy-njs-guide), which required NGINX JavaScript files, key-value stores, and advanced OpenID Connect logic. In the latest NGINX Plus version, the new [OpenID Connect module](https://nginx.org/en/docs/http/ngx_http_oidc_module.html) simplifies this process to just a few directives.{{< /note >}} - ## Prerequisites -The instructions assume you have the following: +- A running [Keycloak](https://www.keycloak.org/) server version compatible with OIDC. -- A running Keycloak server. See the Keycloak documentation for [Getting Started](https://www.keycloak.org/guides#getting-started) and [Server](https://www.keycloak.org/guides#server) configuration instructions. -- An NGINX Plus subscription and NGINX Plus R15 or later. For installation instructions, see the [NGINX Plus Admin Guide](https://docs.nginx.com/nginx/admin-guide/installing-nginx/installing-nginx-plus/). -- The [NGINX JavaScript module](https://www.nginx.com/blog/introduction-nginscript/) (njs), required for handling the interaction between NGINX Plus and the IdP. After installing NGINX Plus, install the module with the command for your operating system. +- An NGINX Plus [subscription](https://www.f5.com/products/nginx/nginx-plus) and NGINX Plus [Release 34](({{< ref "nginx/releases.md#r34" >}})) or later. For installation instructions, see [Installing NGINX Plus](https://docs.nginx.com/nginx/admin-guide/installing-nginx/installing-nginx-plus/). - For Debian and Ubuntu: +- A domain name pointing to your NGINX Plus instance, for example, `demo.example.com`. - ```none - sudo apt install nginx-plus-module-njs - ``` - For CentOS, RHEL, and Oracle Linux: +## Configure Keycloak {#keycloak-setup} - ```shell - sudo yum install nginx-plus-module-njs - ``` +1. Log in to your Keycloak admin console, for example, `https:///auth/admin/`. -- The following directive included in the top-level ("main") configuration context in **/etc/nginx/nginx.conf**, to load the NGINX JavaScript module: +2. In the left navigation, go to **Clients**.then - ```nginx - load_module modules/ngx_http_js_module.so; - ``` +3. Select **Create** and provide the following details: - -## Configuring Keycloak + - Enter a **Client ID**, for example, `nginx-demo-app`. You will need it later when configuring NGINX Plus. -**Note:** The following procedure reflects the Keycloak GUI at the time of publication, but the GUI is subject to change. Use this guide as a reference and adapt to the current Keycloak GUI as necessary. + - Set **Client Protocol** to **openid-connect**. -Create a Keycloak client for NGINX Plus in the Keycloak GUI: + - Select **Save**. -1. Access the Keycloak Admin Console at **http://_keycloak-server-address_:8080/auth/admin/** and log in. +4. In the **Settings** tab of your new client: -2. In the left navigation column, click **Clients**. On the **Clients** page that opens, click the **Create** button in the upper right corner. + - Set **Access Type** to `confidential`. - -3. On the **Add Client** page that opens, enter or select these values, then click the  Save  button. + - Add a **Redirect URI**, for example: + ``` + https://demo.example.com/oidc_callback + ``` + - Select **Save**. - - **Client ID** – The name of the application for which you're enabling SSO (Keycloak refers to it as the “client”). Here we're using NGINX-Plus. - - **Client Protocol** – openid-connect. +5. In the **Credentials** tab, make note of the **Client Secret**. You will need it later when configuring NGINX Plus. - +### Assign Users or Groups -4. On the **NGINX Plus** page that opens, enter or select these values on the Settings tab: +This step is optional, and is necessary if you need to restrict or organize user permissions. - - **Access Type** – confidential - - **Valid Redirect URIs** – The URI of the NGINX Plus instance, including the port number, and ending in **/\_codexch** (in this guide it is https://my-nginx.example.com:443/_codexch) +1. In the **Roles** tab, add a **Client Role**, for example, `nginx-keycloak-role`. - **Notes:** +2. Under **Users**, create a new user or select a user. - - For production, we strongly recommend that you use SSL/TLS (port 443). - - The port number is mandatory even when you're using the default port for HTTP (80) or HTTPS (443). +3. In **Role Mappings**, assign a role to the user within the `nginx-demo-app` client. - +{{< note >}} You will need the values of **Client ID**, **Client Secret**, and **Issuer** in the next steps. {{< /note >}} - -5. Click the Credentials tab and make a note of the value in the **Secret** field. You will copy it into the NGINX Plus configuration file in [Step 4 of _Configuring NGINX Plus_](#nginx-plus-variables). - +## Set up NGINX Plus {#nginx-plus-setup} -6. Click the Roles tab, then click the **Add Role** button in the upper right corner of the page that opens. +With Keycloak configured, you can enable OIDC on NGINX Plus. NGINX Plus serves as the Rely Party (RP) application — a client service that verifies user identity. -7. On the **Add Role** page that opens, type a value in the **Role Name** field (here it is nginx-keycloak-role) and click the  Save  button. +1. Ensure that you are using the latest version of NGINX Plus by running the `nginx -v` command in a terminal: - + ```shell + nginx -v + ``` + The output should match NGINX Plus Release 34 or later: + + ```none + nginx version: nginx/1.27.4 (nginx-plus-r34) + ``` + +2. Ensure that you have the values of the **Client ID**, **Client Secret**, and **Issuer** obtained during [Keycloak Configuration](#keycloak-setup). + +3. In your preferred text editor, open the NGINX configuration file (`/etc/nginx/nginx.conf` for Linux or `/usr/local/etc/nginx/nginx.conf` for FreeBSD). + +4. In the [`http {}`](https://nginx.org/en/docs/http/ngx_http_core_module.html#http) context, make sure your public DNS resolver is specified with the [`resolver`](https://nginx.org/en/docs/http/ngx_http_core_module.html#resolver) directive: By default, NGINX Plus re‑resolves DNS records at the frequency specified by time‑to‑live (TTL) in the record, but you can override the TTL value with the `valid` parameter: + + ```nginx + http { + resolver 10.0.0.1 ipv4=on valid=300s; + + # ... + } + ``` + + +5. In the [`http {}`](https://nginx.org/en/docs/http/ngx_http_core_module.html#http) context, define the Keycloak provider named `keycloak` by specifying the [`oidc_provider {}`](https://nginx.org/en/docs/http/ngx_http_oidc_module.html#oidc_provider) context: + + ```nginx + http { + resolver 10.0.0.1 ipv4=on valid=300s; -8. In the left navigation column, click **Users**. On the **Users** page that opens, either click the name of an existing user, or click the **Add user** button in the upper right corner to create a new user. For complete instructions, see the [Keycloak documentation](https://www.keycloak.org/docs/latest/server_admin/index.html#user-management). + oidc_provider keycloak { + + # ... + + } + # ... + } + ``` - -9. On the management page for the user (here, user01), click the Role Mappings tab. On the page that opens, select NGINX-Plus on the **Client Roles** drop‑down menu. Click nginx-keycloak-role in the **Available Roles** box, then click the **Add selected** button below the box. The role then appears in the **Assigned Roles** and **Effective Roles** boxes, as shown in the screenshot. +6. In the [`oidc_provider {}`](https://nginx.org/en/docs/http/ngx_http_oidc_module.html#oidc_provider) context, specify: - + - your actual Keycloak **Client ID** obtained in [Keycloak Configuration](#keycloak-setup) with the [`client_id`](https://nginx.org/en/docs/http/ngx_http_oidc_module.html#client_id) directive + - your **Client Secret** obtained in [Keycloak Configuration](#keycloak-setup) with the [`client_secret`](https://nginx.org/en/docs/http/ngx_http_oidc_module.html#client_secret) directive - -## Configuring NGINX Plus + - the **Issuer** URL obtained in [Keycloak Configuration](#keycloak-setup) with the [`issuer`](https://nginx.org/en/docs/http/ngx_http_oidc_module.html#client_secret) directive -Configure NGINX Plus as the OpenID Connect relying party: + The `issuer` is typically your Keycloak OIDC URL: -1. Create a clone of the [nginx-openid-connect](https://github.com/nginxinc/nginx-openid-connect) GitHub repository. + `https:///realms/`. - ```shell - git clone https://github.com/nginxinc/nginx-openid-connect - ``` + By default, NGINX Plus creates the metadata URL by appending the `/.well-known/openid-configuration` part to the Issuer URL. If your metadata URL is different, you can explicitly specify it with the [`config_url`](https://nginx.org/en/docs/http/ngx_http_oidc_module.html#config_url) directive. + + - **Important:** All interaction with the IdP is secured exclusively over SSL/TLS, so NGINX must trust the certificate presented by the IdP. By default, this trust is validated against your system’s CA bundle (the default CA store for your Linux or FreeBSD distribution). If the IdP’s certificate is not included in the system CA bundle, you can explicitly specify a trusted certificate or chain with the [`ssl_trusted_certificate`](https://nginx.org/en/docs/http/ngx_http_oidc_module.html#ssl_trusted_certificate) directive so that NGINX can validate and trust the IdP’s certificate. + + ```nginx + http { + resolver 10.0.0.1 ipv4=on valid=300s; + + oidc_provider keycloak { + issuer https:///realms/; + client_id ; + client_secret ; + } + + # ... + } + ``` + +7. Make sure you have configured a [server](https://nginx.org/en/docs/http/ngx_http_core_module.html#server) that corresponds to `demo.example.com`, and there is a [location](https://nginx.org/en/docs/http/ngx_http_core_module.html#location) that [points](https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_pass) to your application (see [Step 10](#oidc_app)) at `http://127.0.0.1:8080` that is going to be OIDC-protected: + + ```nginx + http { + # ... + + server { + listen 443 ssl; + server_name demo.example.com; + + ssl_certificate /etc/ssl/certs/fullchain.pem; + ssl_certificate_key /etc/ssl/private/key.pem; + + location / { + + # ... + + proxy_pass http://127.0.0.1:8080; + } + } + # ... + } + ``` -2. Copy these files from the clone to **/etc/nginx/conf.d**: +8. Protect this [location](https://nginx.org/en/docs/http/ngx_http_core_module.html#location) with Keycloak OIDC by specifying the [`auth_oidc`](https://nginx.org/en/docs/http/ngx_http_oidc_module.html#auth_oidc) directive that will point to the `keycloak` configuration specified in the [`oidc_provider {}`](https://nginx.org/en/docs/http/ngx_http_oidc_module.html#oidc_provider) context in [Step 5](#keycloak-setup-oidc-provider): - - **frontend.conf** - - **openid\_connect.js** - - **openid\_connect.server\_conf** - - **openid\_connect\_configuration.conf** + ```nginx + # ... + location / { + auth_oidc keycloak; - -3. Get the URLs for the authorization endpoint, token endpoint, and JSON Web Key (JWK) file from the Keycloak configuration. Run the following `curl` command in a terminal, piping the output to the indicated `python` command to output the entire configuration in an easily readable format. We've abridged the output to show only the relevant fields. + # ... - ```shell - $ curl https:///auth/realms/master/.well-known/openid-configuration | python -m json.tool - ... - { - "authorization_endpoint": "https:///auth/realms/master/protocol/openid-connect/auth", - ... - "jwks_uri": "https:///auth/realms/master/protocol/openid-connect/certs", - ... - "token_endpoint": "https:///auth/realms/master/protocol/openid-connect/token", - ... + proxy_pass http://127.0.0.1:8080; } + # ... ``` - -4. Using your preferred text editor, open **/etc/nginx/conf.d/openid_connect_configuration.conf**. Change the "default" parameter value of each of the following [map](https://nginx.org/en/docs/http/ngx_http_map_module.html#map) directives to the specified value: +9. Pass the OIDC claims as headers to the application ([Step 10](#oidc_app)) with the [`proxy_set_header`](https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_set_header) directive. These claims are extracted from the ID token returned by Keycloak: - - `map $host $oidc_authz_endpoint` – Value of `authorization_endpoint` from [Step 3](#nginx-plus-urls) (in this guide, `https:///auth/realms/master/protocol/openid-connect/auth`) - - `map $host $oidc_token_endpoint` – Value of `token_endpoint` from [Step 3](#nginx-plus-urls) (in this guide, `https:///auth/realms/master/protocol/openid-connect/token`) - - `map $host $oidc_client` – Value in the **Client ID** field from [Step 3 of _Configuring Keycloak_](#keycloak-client-id) (in this guide, `NGINX Plus`) - - `map $host $oidc_client_secret` – Value in the **Secret** field from [Step 5 of _Configuring Keycloak_](#keycloak-secret) (in this guide, ``) - - `map $host $oidc_hmac_key` – A unique, long, and secure phrase + - [`$oidc_claim_sub`](https://nginx.org/en/docs/http/ngx_http_oidc_module.html#var_oidc_claim_) - a unique `Subject` identifier assigned for each user by Keycloak -5. Configure the JWK file. The procedure depends on which version of NGINX Plus you are using. + - [`$oidc_claim_email`](https://nginx.org/en/docs/http/ngx_http_oidc_module.html#var_oidc_claim_) the e-mail address of the user + + - [`$oidc_claim_name`](https://nginx.org/en/docs/http/ngx_http_oidc_module.html#var_oidc_claim_) - the full name of the user + + - any other OIDC claim using the [`$oidc_claim_ `](https://nginx.org/en/docs/http/ngx_http_oidc_module.html#var_oidc_claim_) variable + + + ```nginx + # ... + location / { + auth_oidc keycloak; + + proxy_set_header sub $oidc_claim_sub; + proxy_set_header email $oidc_claim_email; + proxy_set_header name $oidc_claim_name; + + proxy_pass http://127.0.0.1:8080; + } + # ... + ``` + + +10. Create a simple test application referenced by the `proxy_pass` directive which returns the authenticated user's full name and email upon successful authentication: + + ```nginx + # ... + server { + listen 8080; + + location / { + return 200 "Hello, $http_name!\nEmail: $http_email\nSub: $http_sub\n"; + default_type text/plain; + } + } + ``` +11. Save the NGINX configuration file and reload the configuration: + ```nginx + nginx -s reload + ``` + +### Complete Example + +This configuration example summarizes the steps outlined above. It includes only essential settings such as specifying the DNS resolver, defining the OIDC provider, configuring SSL, and proxying requests to an internal server. + +```nginx +http { + # Use a public DNS resolver for Issuer discovery, etc. + resolver 10.0.0.1 ipv4=on valid=300s; + + oidc_provider keycloak { + # The 'issuer' typically matches your Keycloak realm's base URL: + # For example: https:///realms/ + issuer https:///realms/master; + + # Replace with your actual Keycloak client_id and secret + client_id ; + client_secret ; + + # If the .well-known endpoint can’t be derived automatically, + # specify config_url: + # config_url https:///realms/master/.well-known/openid-configuration; + } + + server { + listen 443 ssl; + server_name demo.example.com; + + ssl_certificate /etc/ssl/certs/fullchain.pem; + ssl_certificate_key /etc/ssl/private/key.pem; + + location / { + # Protect this location with Keycloak OIDC + auth_oidc keycloak; + + # Forward OIDC claims as headers if desired + proxy_set_header sub $oidc_claim_sub; + proxy_set_header email $oidc_claim_email; + proxy_set_header name $oidc_claim_name; + + proxy_pass http://127.0.0.1:8080; + } + } + + server { + # Simple test backend + listen 8080; + + location / { + return 200 "Hello, $http_name!\nEmail: $http_email\nSub: $http_sub\n"; + default_type text/plain; + } + } +} +``` - - In NGINX Plus R17 and later, NGINX Plus can read the JWK file directly from the URL reported as `jwks_uri` in [Step 3](#nginx-plus-urls). Change **/etc/nginx/conf.d/frontend.conf** as follows: +### Testing - 1. Comment out (or remove) the [auth_jwt_key_file](http://nginx.org/en/docs/http/ngx_http_auth_jwt_module.html#auth_jwt_key_file) directive. +1. Open https://demo.example.com/ in a browser. You should be redirected to Keycloak’s login page for your realm. - 2. Uncomment the [auth_jwt_key_request](http://nginx.org/en/docs/http/ngx_http_auth_jwt_module.html#auth_jwt_key_request) directive. (Its parameter, `/_jwks_uri`, refers to the value of the `$oidc_jwt_keyfile` variable, which you set in the next step.) - 3. Change the "default" parameter of the `map $host $oidc_jwt_keyfile` directive to the value reported in the `jwks_uri` field in [Step 3](#nginx-plus-urls) (in this guide, `https:///auth/realms/master/protocol/openid-connect/certs`). +2. Enter valid Keycloak credentials for a user assigned to the `nginx-demo-app` client. +Upon successful sign-in, Keycloak redirects you back to NGINX Plus, and you will see the proxied application content (for example, “Hello, Jane Doe!”). - - In NGINX Plus R16 and earlier, the JWK file must be on the local disk. (You can also use this method with NGINX Plus R17 and later if you wish.) - 1. Copy the JSON contents from the JWK file named in the `jwks_uri` field in [Step 3](#nginx-plus-urls) (in this guide, `https:///auth/realms/master/protocol/openid-connect/certs`) to a local file (for example, `/etc/nginx/my_keycloak_jwk.json`). - 2. In **/etc/nginx/conf.d/openid_connect_configuration.conf**, change the "default" parameter of the `map $host $oidc_jwt_keyfile` directive to the local file path. +## Legacy njs-based Keycloak Solution {#legacy-njs-guide} -6. Confirm that the user named by the [user](http://nginx.org/en/docs/ngx_core_module.html#user) directive in the NGINX Plus configuration (in **/etc/nginx/nginx.conf** by convention) has read permission on the JWK file. +If you are running NGINX Plus R33 and earlier or if you still need the njs-based solution, refer to the [Legacy njs-based Keycloak Guide]({{< ref "nginx/deployment-guides/single-sign-on/oidc-njs/keycloak.md" >}}) for details. The solution uses the [`nginx-openid-connect`](https://github.com/nginxinc/nginx-openid-connect) GitHub repository and NGINX JavaScript files. - -## Testing -In a browser, enter the address of your NGINX Plus instance and try to log in using the credentials of a user mapped to the role for NGINX Plus (see [Step 9 of _Configuring Keycloak_](#keycloak-users)). +## See Also - +- [NGINX Plus Native OIDC Module Reference documentation](https://nginx.org/en/docs/http/ngx_http_oidc_module.html) - -## Troubleshooting +- [Release Notes for NGINX Plus R34]({{< ref "nginx/releases.md#r34" >}}) -See the [**Troubleshooting**](https://github.com/nginxinc/nginx-openid-connect#troubleshooting) section at the nginx-openid-connect repository on GitHub. -### Revision History +## Revision History -- Version 2 (March 2020) – Updates to _Configuring NGINX Plus_ section -- Version 1 (November 2019) – Initial version (NGINX Plus Release 19) +- Version 1 (March 2025) – Initial version (NGINX Plus Release 34) diff --git a/content/nginx/deployment-guides/single-sign-on/oidc-njs/_index.md b/content/nginx/deployment-guides/single-sign-on/oidc-njs/_index.md new file mode 100644 index 000000000..3b7a3b79c --- /dev/null +++ b/content/nginx/deployment-guides/single-sign-on/oidc-njs/_index.md @@ -0,0 +1,9 @@ +--- +description: Learn how to use OpenID Connect (OIDC) Provider Servers and Services + to enable single sign-on for applications proxied by F5 NGINX Plus. +menu: + docs: + parent: NGINX Plus +title: Legacy njs-based Single Sign-On Solutions +weight: 990 +--- diff --git a/content/nginx/deployment-guides/single-sign-on/oidc-njs/active-directory-federation-services.md b/content/nginx/deployment-guides/single-sign-on/oidc-njs/active-directory-federation-services.md new file mode 100644 index 000000000..80c776c47 --- /dev/null +++ b/content/nginx/deployment-guides/single-sign-on/oidc-njs/active-directory-federation-services.md @@ -0,0 +1,157 @@ +--- +description: Enable OpenID Connect-based single-sign for applications proxied by NGINX + Plus, using Microsoft AD FS as the identity provider (IdP). +docs: DOCS-463 +doctypes: +- task +title: Single Sign-On with Microsoft AD FS and njs +toc: false +weight: 100 +--- + +{{< note >}} This guide applies to NGINX Plus [Release 15]({{< ref "nginx/releases.md#r15" >}}) and later, based on the [`nginx-openid-connect`](https://github.com/nginxinc/nginx-openid-connect) GitHub repo. Starting with NGINX Plus [Release 34]({{< ref "nginx/releases.md#r34" >}}), use the simpler solution with the [native OpenID connect module](https://nginx.org/en/docs/http/ngx_http_oidc_module.html). + +See [Single Sign-On With Microsoft AD FS]({{< ref "nginx/deployment-guides/single-sign-on/active-directory-federation-services.md" >}}) for details.{{< /note >}} + +This guide explains how to enable single sign-on (SSO) for applications being proxied by F5 NGINX Plus. The solution uses OpenID Connect as the authentication mechanism, with [Microsoft Active Directory Federation Services](https://docs.microsoft.com/en-us/windows-server/identity/active-directory-federation-services) (AD FS) as the identity provider (IdP) and NGINX Plus as the relying party. + +{{< see-also >}}{{< readfile file="includes/nginx-openid-repo-note.txt" markdown="true" >}}{{< /see-also >}} + + +## Prerequisites + +The instructions assume you have the following: + +- A running deployment of AD FS, either on‑premises or in Azure. +- An NGINX Plus subscription and NGINX Plus R15 or later. For installation instructions, see the [NGINX Plus Admin Guide](https://docs.nginx.com/nginx/admin-guide/installing-nginx/installing-nginx-plus/). +- The [NGINX JavaScript module](https://www.nginx.com/blog/introduction-nginscript/) (njs), required for handling the interaction between NGINX Plus and the IdP. After installing NGINX Plus, install the module with the command for your operating system. + + For Debian and Ubuntu: + + ```none + sudo apt install nginx-plus-module-njs + ``` + + For CentOS, RHEL, and Oracle Linux: + + ```shell + sudo yum install nginx-plus-module-njs + ``` + +- The following directive included in the top-level ("main") configuration context in **/etc/nginx/nginx.conf**, to load the NGINX JavaScript module: + + ```nginx + load_module modules/ngx_http_js_module.so; + ``` + + +## Configuring AD FS + +Create an AD FS application for NGINX Plus: + +1. Open the AD FS Management window. In the navigation column on the left, right‑click on the **Application Groups** folder and select Add Application Group from the drop‑down menu. + + The Add Application Group Wizard window opens. The left navigation column shows the steps you will complete to add an application group. + +2. In the **Welcome** step, type the application group name in the **Name** field. Here we are using ADFSSSO. In the **Template** field, select **Server application** under Standalone applications. Click the  Next >  button. + + + + +3. In the **Server application** step: + + 1. Make a note of the value in the **Client Identifier** field. You will add it to the NGINX Plus configuration in [Step 4 of _Configuring NGINX Plus_](#nginx-plus-variables).
+ + 2. In the **Redirect URI** field, type the URI of the NGINX Plus instance including the port number, and ending in **/\_codexch**. Here we’re using https://my-nginx.example.com:443/\_codexch. Click the  Add  button. + + **Notes:** + + - For production, we strongly recommend that you use SSL/TLS (port 443). + - The port number is mandatory even when you're using the default port for HTTP (80) or HTTPS (443). + +3. Click the  Next >  button. + + + + +4. In the Configure Application Credentials step, click the Generate a shared secret checkbox. Make a note of the secret that AD FS generates (perhaps by clicking the Copy to clipboard button and pasting the clipboard content into a file). You will add the secret to the NGINX Plus configuration in [Step 4 of _Configuring NGINX Plus_](#nginx-plus-variables). Click the  Next >  button. + + + +5. In the **Summary** step, verify that the information is correct, make any necessary corrections to previous steps, and click the  Next >  button. + + + +## Configuring NGINX Plus + +Configure NGINX Plus as the OpenID Connect relying party: + +1. Create a clone of the [nginx-openid-connect](https://github.com/nginxinc/nginx-openid-connect) GitHub repository. + + ```shell + git clone https://github.com/nginxinc/nginx-openid-connect + ``` + +2. Copy these files from the clone to **/etc/nginx/conf.d**: + + - **frontend.conf** + - **openid\_connect.js** + - **openid\_connect.server\_conf** + - **openid\_connect\_configuration.conf** + + +3. Get the URLs for the authorization endpoint, token endpoint, and JSON Web Key (JWK) file from the AD FS configuration. Run the following `curl` command in a terminal, piping the output to the indicated `python` command to output the entire configuration in an easily readable format. We've abridged the output to show only the relevant fields. + + ```shell + $ curl https:///oidc/adfs/.well-known/openid-configuration | python -m json.tool + { + ... + "authorization_endpoint": "https:///oidc/adfs/auth", + ... + "jwks_uri": "https:///oidc/adfs/certs", + ... + "token_endpoint": "https:///oidc/adfs/token", + ... + } + ``` + + +4. In your preferred text editor, open **/etc/nginx/conf.d/frontend.conf**. Change the "default" parameter value of each of the following [map](https://nginx.org/en/docs/http/ngx_http_map_module.html#map) directives to the specified value: + + - `map $host $oidc_authz_endpoint` – Value of `authorization_endpoint` from [Step 3](#nginx-plus-urls) (in this guide, `https:///oidc/adfs/auth`) + - `map $host $oidc_token_endpoint` – Value of `token_endpoint` from [Step 3](#nginx-plus-urls) (in this guide, `https:///oidc/adfs/token`) + - `map $host $oidc_client` – Value in the **Client ID** field from [Step 3 of _Configuring AD FS_](#ad-fs-server-application) (in this guide, `3e23f0eb-9329-46ff-9d37-6ad24afdfaeb`) + - `map $host $oidc_client_secret` – Value in the **Client secret** field from [Step 4 of _Configuring AD FS_](#ad-fs-configure-application-credentials) (in this guide, `NUeuULtSCjgXTGSkq3ZwEeCOiig4-rB2XiW_W`) + - `map $host $oidc_hmac_key` – A unique, long, and secure phrase + +5. Configure the JWK file. The procedure depends on which version of NGINX Plus you are using. + + - In NGINX Plus R17 and later, NGINX Plus can read the JWK file directly from the URL reported as `jwks_uri` in [Step 3](#nginx-plus-urls). Change **/etc/nginx/conf.d/frontend.conf** as follows: + + 1. Comment out (or remove) the [auth_jwt_key_file](http://nginx.org/en/docs/http/ngx_http_auth_jwt_module.html#auth_jwt_key_file) directive. + 2. Uncomment the [auth_jwt_key_request](http://nginx.org/en/docs/http/ngx_http_auth_jwt_module.html#auth_jwt_key_request) directive. (Its parameter, `/_jwks_uri`, refers to the value of the `$oidc_jwt_keyfile` variable, which you set in the next step.) + 3. Change the second parameter of the `set $oidc_jwt_keyfile` directive to the value reported in the `jwks_uri` field in [Step 3](#nginx-plus-urls) (in this guide, `https:///oidc/adfs/certs`). + + - In NGINX Plus R16 and earlier, the JWK file must be on the local disk. (You can also use this method with NGINX Plus R17 and later if you wish.) + + 1. Copy the JSON contents from the JWK file named in the `jwks_uri` field in [Step 3](#nginx-plus-urls) (in this guide, `https:///oidc/adfs/certs`) to a local file (for example, `/etc/nginx/my_adfs_jwk.json`). + 2. In **/etc/nginx/conf.d/frontend.conf**, change the second parameter of the `set $oidc_jwt_keyfile` directive to the local file path. + +6. Confirm that the user named by the [user](http://nginx.org/en/docs/ngx_core_module.html#user) directive in the NGINX Plus configuration (in **/etc/nginx/nginx.conf** by convention) has read permission on the JWK file. + + +## Testing + +In a browser, enter the address of your NGINX Plus instance and try to log in using the credentials of a user who has access to the application. + + + + +## Troubleshooting + +See the [**Troubleshooting**](https://github.com/nginxinc/nginx-openid-connect#troubleshooting) section at the nginx-openid-connect repository on GitHub. + +### Revision History + +- Version 2 (March 2020) – Updates to _Configuring NGINX Plus_ section +- Version 1 (December 2019) – Initial version (NGINX Plus Release 20) diff --git a/content/nginx/deployment-guides/single-sign-on/oidc-njs/auth0.md b/content/nginx/deployment-guides/single-sign-on/oidc-njs/auth0.md new file mode 100644 index 000000000..4e192b0aa --- /dev/null +++ b/content/nginx/deployment-guides/single-sign-on/oidc-njs/auth0.md @@ -0,0 +1,187 @@ +--- +description: Learn how to enable single sign-on (SSO) with [Auth0](https://auth0.com/) + for applications proxied by F5 NGINX Plus. +docs: DOCS-884 +doctypes: +- tutorial +tags: +- docs +title: Single Sign-On With Auth0 and njs +toc: false +weight: 100 +--- + +{{< note >}} This guide applies to NGINX Plus [Release 15]({{< ref "nginx/releases.md#r15" >}}) and later, based on the [`nginx-openid-connect`](https://github.com/nginxinc/nginx-openid-connect) GitHub repo. Starting with NGINX Plus [Release 34]({{< ref "nginx/releases.md#r34" >}}), use the simpler solution with the [native OpenID connect module](https://nginx.org/en/docs/http/ngx_http_oidc_module.html). + +See [Single Sign-On With Auth0]({{< ref "nginx/deployment-guides/single-sign-on/auth0.md" >}}) for details.{{< /note >}} + +You can use F5 NGINX Plus with [Auth0](https://auth0.com/) and OpenID Connect to enable single sign-on (SSO) for your proxied applications. By following the steps in this guide, you will learn how to set up SSO using OpenID Connect as the authentication mechanism, with Auth0 as the identity provider (IdP), and NGINX Plus as the relying party. + +{{< see-also >}}{{< readfile file="includes/nginx-openid-repo-note.txt" markdown="true" >}}{{< /see-also >}} + +## Prerequisites + +To complete the steps in this guide, you need the following: + +- An Auth0 tenant with administrator privileges. +- [NGINX Plus](https://www.f5.com/products/nginx/nginx-plus) with a valid subscription. +- The [NGINX JavaScript module](https://www.nginx.com/products/nginx/modules/nginx-javascript/) (`njs`) -- the `njs` module handles the interaction between NGINX Plus and Auth0. + +## Install NGINX Plus and the njs Module {#install-nginx-plus-njs} + +1. If you do not already have NGINX Plus installed, follow the steps in the [NGINX Plus Admin Guide](https://docs.nginx.com/nginx/admin-guide/installing-nginx/installing-nginx-plus/) to do so. +2. Install the NGINX JavaScript module by following the steps in the [`njs` installation guide](https://nginx.org/en/docs/njs/install.html). +3. Add the following directive to the top-level ("main") configuration context in the NGINX Plus configuration (`/etc/nginx/nginx.conf`) to load the `njs` module: + + ```Nginx configuration file + load_module modules/ngx_http_js_module.so; + ``` + +## Configure Auth0 {#config-auth0} + +Take the steps in this section to create a new application for NGINX Plus. + +{{< note >}} This section contains images that reflect the state of the Auth0 web interface at the time of publication. The actual Auth0 GUI may differ from the examples shown here. Use this guide as a reference and adapt the instructions to suit the current Auth0 GUI as necessary.{{< /note >}} + +### Create a new Auth0 Application {#create-auth0-app} + +1. Log in to your Auth0 Dashboard at [manage.auth0.com](https://manage.auth0.com/). +1. Select **Applications > Applications** from the sidebar menu. +1. On the **Applications** page, select the **Create Application** button. +1. In the **Create application** window, provide the information listed below and then select **Create**. + + - **Name**: A name for the application, for example "nginx-plus-app". + - **Application Type**: **Regular Web Applications** + + {{< img src="/img/sso/auth0/sso-auth0-create-app.png" alt="image showing the Create application window in the Auth0 dashboard" >}} + +### Set up the Web Application {#web-app-setup} + +In this section, you'll set up a web application that follows the Auth0 [Authorization Code Flow](https://auth0.com/docs/get-started/authentication-and-authorization-flow/authorization-code-flow). + +1. On the **Application** page in the [Auth0 dashboard](https://manage.auth0.com/), select your web application. +1. Select the **Settings** tab for your application. +1. Make note of the Client ID and Client Secret displayed in the **Basic Information** section. + + {{< img src="/img/sso/auth0/sso-auth0-app.png" alt="image showing the basic information section of the web application settings in the Auth0 dashboard" >}} + +1. In the **Application URIs** section, provide the URI of the NGINX Plus instance in the **Allowed Callback URLs** field. + + - The URL must include the port number and end in **/_codexch**. In our example, we used the URL `http://nginx-plus-app:8010/_codexch`. + - The port is always required, even if you use the default port for HTTP (`80`) or HTTPS (`443`). + - The use of SSL/TLS (`443`) is strongly recommended for production environments. + + {{< img src="/img/sso/auth0/sso-auth0-app-settings.png" alt="image showing the Application URIs settings in the Auth0 dashboard" >}} + +1. In the **Advanced Settings** section, select the **Endpoints** tab. +1. Make note of the **OpenID Configuration** URL. + + {{< img src="/img/sso/auth0/sso-auth0-app-advanced-settings.png" alt="image showing the Advanced Application Settings section of the Auth0 dashboard" >}} + +1. Select **Save Changes**. + +### Set up Authentication {#authn-setup} + +{{< note >}}For the purposes of this guide, we will add a new Auth0 user database and user account to use for testing. + +You can set up authentication using any of the available [Auth0 identity providers](https://auth0.com/docs/authenticate/identity-providers). {{< /note >}} + +To set up a new user database and add a user account to it, take the steps below. + +1. Log in to the [Auth0 dashboard](https://manage.auth0.com/) and select **Authentication > Database** from the sidebar menu. +1. Select the **Create DB Connection** button. +1. Provide a **Name** for the database connection, then select **Create**. +1. On the **Database** page, select the **Applications** tab. Then, select the toggle button next to the [application you created earlier](#create-a-new-auth0-application). + + {{< img src="/img/sso/auth0/sso-auth0-db-app.png" alt="image showing the Applications settings for an OIDC Authentication database in the Auth0 dashboard" >}} + +1. In the sidebar menu, select **User Management > Users**. +1. On the **Users** page, select the **Create User** button. +1. In the **Create user** window, provide the following information, then select **Create**. + - **Email**: user's email + - **Password**: a password for the user account + - **Connection**: select your **database** from the list. + + {{< img src="/img/sso/auth0/sso-auth0-create-user.png" alt="image showing the Create User settings window in the Auth0 dashboard" >}} + +The user should receive an email to the email address provided. Once the user verifies their account by clicking on the link in the email, the account creation process is complete. + +## Set up NGINX Plus {#nginx-plus-setup} + +Take the steps in this section to set up NGINX Plus as the OpenID Connect relying party. + +### Configure NGINX OpenID Connect {#nginx-plus-oidc-config} + +1. Clone the [nginx-openid-connect](https://github.com/nginxinc/nginx-openid-connect) GitHub repository, or download the repo files. + + ```bash + git clone https://github.com/nginxinc/nginx-openid-connect.git + ``` + +1. Run the *configure.sh* script, which will update the NGINX configuration files with the values for your Auth0 application. + + For example: + + ```bash + ./nginx-openid-connect/configure.sh \ + --auth_jwt_key request \ + --client_id Nhotzxx...IERmUi \ + --client_secret 6ZHd0j_r...UtDZ5bkdu \ + https://.us.auth0.com/.well-known/openid-configuration + ``` + +1. In the `frontend.conf` file, update the **my_backend** upstream with the address of the application that you want to add OIDC authorization to. + + For example: + + ```Nginx configuration file + upstream my_backend { + zone my_backend 64k; + server my-backend-app.com:80; + } + ``` + +1. In the *openid_connect.server_conf* file, add the [`proxy_set_header`](https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_set_header) directive to the `/_jwks_uri` and `/_token` locations to `Accept-Encoding "gzip"`, as shown below. + + ```Nginx configuration file + ... + location = /_jwks_uri { + ... + proxy_set_header Accept-Encoding "gzip" + } + ... + location = /_token { + ... + proxy_set_header Accept-Encoding "gzip" + } + ... + ``` + +1. Copy the following files to the */etc/nginx/conf.d* directory on the host machine where NGINX Plus is installed: + + - `frontend.conf` + - `openid_connect.js` + - `openid_connect.server_conf` + - `openid_connect_configuration.conf` + +1. Reload the NGINX configuration: + + ```bash + sudo nginx -s reload + ``` + +## Test the Setup + +1. In a browser, enter the address of your NGINX Plus instance. You should be directed to the Auth0 login page, as shown in the example below. + + {{< img src="/img/sso/auth0/sso-auth0-login-test.png" alt="image showing an example Auth0 login screen that contains username and password fields" >}} + +1. You should be able to log in using the credentials of the user account that you created in the Auth0 database. + +## Troubleshooting + +Refer to the [Troubleshooting](https://github.com/nginxinc/nginx-openid-connect#troubleshooting) section in the `nginx-openid-connect` repository on GitHub. + +## Revision History + +- Version 1 (May 2022) - Initial version diff --git a/content/nginx/deployment-guides/single-sign-on/oidc-njs/cognito.md b/content/nginx/deployment-guides/single-sign-on/oidc-njs/cognito.md new file mode 100644 index 000000000..19087ffd3 --- /dev/null +++ b/content/nginx/deployment-guides/single-sign-on/oidc-njs/cognito.md @@ -0,0 +1,193 @@ +--- +description: Enable OpenID Connect-based single-sign for applications proxied by NGINX + Plus, using Amazon Cognito as the identity provider (IdP). +docs: DOCS-464 +doctypes: +- task +title: Single Sign-On with Amazon Cognito and njs +toc: false +weight: 100 +--- + +{{< note >}} This guide applies to NGINX Plus [Release 15]({{< ref "nginx/releases.md#r15" >}}) and later, based on the [`nginx-openid-connect`](https://github.com/nginxinc/nginx-openid-connect) GitHub repo. Starting with NGINX Plus [Release 34]({{< ref "nginx/releases.md#r34" >}}), use the simpler solution with the [native OpenID connect module](https://nginx.org/en/docs/http/ngx_http_oidc_module.html). + +See [Single Sign-On With Amazon Cognito]({{< ref "nginx/deployment-guides/single-sign-on/cognito.md" >}}) for details.{{< /note >}} + +This guide explains how to enable single sign‑on (SSO) for applications being proxied by F5 NGINX Plus. The solution uses OpenID Connect as the authentication mechanism, with [Amazon Cognito](https://aws.amazon.com/cognito/) as the identity provider (IdP), and NGINX Plus as the relying party. + +{{< see-also >}}{{< readfile file="includes/nginx-openid-repo-note.txt" markdown="true" >}}{{< /see-also >}} + + + +## Prerequisites + +The instructions assume you have the following: + +- An [AWS account](https://aws.amazon.com/premiumsupport/knowledge-center/create-and-activate-aws-account/). +- An NGINX Plus subscription and NGINX Plus R15 or later. For installation instructions, see the [NGINX Plus Admin Guide](https://docs.nginx.com/nginx/admin-guide/installing-nginx/installing-nginx-plus/). +- The [NGINX JavaScript module](https://www.nginx.com/blog/introduction-nginscript/) (njs), required for handling the interaction between NGINX Plus and the IdP. After installing NGINX Plus, install the module with the command for your operating system. + + For Debian and Ubuntu: + + ```none + sudo apt install nginx-plus-module-njs + ``` + + For CentOS, RHEL, and Oracle Linux: + + ```shell + sudo yum install nginx-plus-module-njs + ``` + +- The following directive included in the top-level ("main") configuration context in **/etc/nginx/nginx.conf**, to load the NGINX JavaScript module: + + ```nginx + load_module modules/ngx_http_js_module.so; + ``` + + +## Configuring Amazon Cognito + +**Note:** The following procedure reflects the Cognito GUI at the time of publication, but the GUI is subject to change. Use this guide as a reference and adapt to the current Cognito GUI as necessary. + +Create a new application for NGINX Plus in the Cognito GUI: + +1. Log in to your AWS account, open the AWS Management Console ([console.aws.amazon.com](https://console.aws.amazon.com)), and navigate to the Cognito dashboard (you can, for example, click **Cognito** in the **Security, Identity, & Compliance** section of the **Services** drop‑down menu). + +2. On the Cognito dashboard, click **Manage User Pools** to open the **Your User Pools** window. Click the  Create a user pool  button or the highlighted phrase. + + + +3. In the **Create a user pool** window that opens, type a value in the **Pool name** field (in this guide, it's nginx-plus-pool), then click the Review defaults button. + + + + +4. On the **Review** tab which opens, click Add app client... in the **App clients** field near the bottom. + + + +5. On the **App clients** tab which opens, click Add an app client. + +6. On the **Which app clients will have access to this user pool?** window which opens, enter a value (in this guide, nginx-plus-app) in the App client name field. Make sure the Generate client secret box is checked, then click the  Create app client  button. + + + +7. On the confirmation page which opens, click Return to pool details to return to the **Review** tab. On that tab click the  Create pool  button at the bottom. (The screenshot in [Step 4](#cognito-review-tab) shows the button.) + + +8. On the details page which opens to confirm the new user pool was successfully created, make note of the value in the **Pool Id** field; you will add it to the NGINX Plus configuration in [Step 3 of _Configuring NGINX Plus_](#nginx-plus-variables). + + 'General settings' tab in Amazon Cognito GUI + + +9. Click Users and groups in the left navigation column. In the interface that opens, designate the users (or group of users, on the **Groups** tab) who will be able to use SSO for the app being proxied by NGINX Plus. For instructions, see the Cognito documentation about [creating users](https://docs.aws.amazon.com/cognito/latest/developerguide/how-to-create-user-accounts.html), [importing users](https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pools-using-import-tool.html), or [adding a group](https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pools-user-groups.html). + + 'Users and groups' tab in Amazon Cognito GUI + +10. Click **App clients** in the left navigation bar. On the tab that opens, click the Show Details button in the box labeled with the app client name (in this guide, nginx-plus-app). + + 'App clients' tab in Amazon Cognito GUI + + +11. On the details page that opens, make note of the values in the App client id and App client secret fields. You will add them to the NGINX Plus configuration in [Step 3 of _Configuring NGINX Plus_](#nginx-plus-variables). + + + +12. Click App client settings in the left navigation column. In the tab that opens, perform the following steps: + + 1. In the Enabled Identity Providers section, click the Cognito User Pool checkbox (the **Select all** box gets checked automatically). + 2. In the **Callback URL(s)** field of the Sign in and sign out URLs section, type the URI of the NGINX Plus instance including the port number, and ending in **/\_codexch**. Here we’re using https://my-nginx-plus.example.com:443/_codexch. + + **Notes:** + + - For production, we strongly recommend that you use SSL/TLS (port 443). + - The port number is mandatory even when you're using the default port for HTTP (80) or HTTPS (443). + + 3. In the **OAuth 2.0** section, click the Authorization code grant checkbox under Allowed OAuth Flows and the **email**, **openid**, and **profile** checkboxes under Allowed OAuth Scopes. + 4. Click the  Save changes  button. + + + + +13. Click **Domain name** in the left navigation column. In the tab that opens, type a domain prefix in the **Domain prefix** field under Amazon Cognito domain (in this guide, my-nginx-plus). Click the  Save changes  button. + + + + +## Configuring NGINX Plus + +Configure NGINX Plus as the OpenID Connect relying party: + +1. Create a clone of the [nginx-openid-connect](https://github.com/nginxinc/nginx-openid-connect) GitHub repository. + + ```shell + git clone https://github.com/nginxinc/nginx-openid-connect + ``` + +2. Copy these files from the clone to **/etc/nginx/conf.d**: + + - **frontend.conf** + - **openid_connect.js** + - **openid_connect.server\_conf** + + +3. In your preferred text editor, open **/etc/nginx/conf.d/frontend.conf**. Change the second parameter of each of the following [set](http://nginx.org/en/docs/http/ngx_http_rewrite_module.html#set) directives to the specified value. + + The `` variable is the full value in the **Domain prefix** field in [Step 13 of _Configuring Amazon Cognito_](#cognito-domain-name). In this guide it is https://my-nginx-plus.auth.us-east-2.amazoncognito.com. + + - `set $oidc_authz_endpoint` – `/oauth2/authorize` + - `set $oidc_token_endpoint` – `/oauth2/token` + - `set $oidc_client` – Value in the App client id field from [Step 11 of _Configuring Amazon Cognito_](#cognito-app-client-id-secret) (in this guide, `2or4cs8bjo1lkbq6143tqp6ist`) + - `set $oidc_client_secret` – Value in the App client secret field from [Step 11 of _Configuring Amazon Cognito_](#cognito-app-client-id-secret) (in this guide, `1k63m3nrcnu...`) + - `set $oidc_hmac_key` – A unique, long, and secure phrase + +4. Configure the JWK file. The file's URL is + + **https://cognito-idp.**_region_**.amazonaws.com/**_User-Pool-ID_**/.well-known/jwks.json** + + where + + - _region_ is the same AWS region name as in the `` variable used in [Step 3](#nginx-plus-variables) + - _User-Pool-ID_ is the value in the **Pool Id** field in [Step 8 of _Configuring Amazon Cognito_](#cognito-pool-id) + + In this guide, the URL is + + https://cognito-idp.us-east-2.amazonaws.com/us-east-2_mLoGHJpOs/.well-known/jwks.json. + + The method for configuring the JWK file depends on which version of NGINX Plus you are using: + + - In NGINX Plus R17 and later, NGINX Plus can read the JWK file directly. Change **/etc/nginx/conf.d/frontend.conf** as follows: + + 1. Comment out (or remove) the [auth_jwt_key_file](http://nginx.org/en/docs/http/ngx_http_auth_jwt_module.html#auth_jwt_key_file) directive. + 2. Uncomment the [auth_jwt_key_request](http://nginx.org/en/docs/http/ngx_http_auth_jwt_module.html#auth_jwt_key_request) directive. (Its parameter, `/_jwks_uri`, refers to the value of the `$oidc_jwt_keyfile` variable, which you set in the next step.) + 3. Change the second parameter of the `set $oidc_jwt_keyfile` directive to the URL of the JWK file (`https://cognito-idp.../.well-known/jwks.json`). + + - In NGINX Plus R16 and earlier, the JWK file must be on the local disk. (You can also use this method with NGINX Plus R17 and later if you wish.) + + 1. Copy the JSON contents from the JWK file (****) to a local file (for example, **/etc/nginx/my\_cognito\_jwk.json**). + 2. In **/etc/nginx/conf.d/frontend.conf**, change the second parameter of the `set $oidc_jwt_keyfile` directive to the local file path. + +5. At the time of publication, Cognito does not support the OpenID **offline_access** scope. Open **/etc/nginx/conf.d/openid\_connect.server\_conf** in a text editor and remove `+offline_access` from the list of scopes on line 10, so that it looks like this: + + ```nginx + return 302 "$oidc_authz_endpoint?response_type=code&scope=openid+profile+email&client_id=$oidc_clientaws...; + ``` + +6. Confirm that the user named by the [user](http://nginx.org/en/docs/ngx_core_module.html#user) directive in the NGINX Plus configuration (in **/etc/nginx/nginx.conf** by convention) has read permission on the JWK file. + + +## Testing + +In a browser, enter the address of your NGINX Plus instance and try to log in using the credentials of a user assigned to the application (see [Step 9 in _Configuring Amazon Cognito_](#cognito-users)). The NGINX logo that appears in the screenshot was added on Cognito's **UI customization** tab (not shown in this guide). + + + + +## Troubleshooting + +See the [**Troubleshooting**](https://github.com/nginxinc/nginx-openid-connect#troubleshooting) section at the nginx-openid-connect repository on GitHub. + +### Revision History + +- Version 1 (March 2020) – Initial version (NGINX Plus Release 20) diff --git a/content/nginx/deployment-guides/single-sign-on/oidc-njs/keycloak.md b/content/nginx/deployment-guides/single-sign-on/oidc-njs/keycloak.md new file mode 100644 index 000000000..ec72125fc --- /dev/null +++ b/content/nginx/deployment-guides/single-sign-on/oidc-njs/keycloak.md @@ -0,0 +1,172 @@ +--- +description: Enable OpenID Connect-based single-sign for applications proxied by NGINX + Plus, using Keycloak as the identity provider (IdP). +docs: DOCS-465 +doctypes: +- task +title: Single Sign-On with Keycloak and njs +toc: false +weight: 100 +--- + +{{< note >}} This guide applies to NGINX Plus [Release 15]({{< ref "nginx/releases.md#r15" >}}) and later, based on the [`nginx-openid-connect`](https://github.com/nginxinc/nginx-openid-connect) GitHub repo. Starting with NGINX Plus [Release 34]({{< ref "nginx/releases.md#r34" >}}), use the simpler solution with the [native OpenID connect module](https://nginx.org/en/docs/http/ngx_http_oidc_module.html). + +See [Single Sign-On With Keycloak]({{< ref "nginx/deployment-guides/single-sign-on/keycloak.md" >}}) for details.{{< /note >}} + +This guide explains how to enable single sign-on (SSO) for applications being proxied by F5 NGINX Plus. The solution uses OpenID Connect as the authentication mechanism, with [Keycloak](https://www.keycloak.org/) as the identity provider (IdP), and NGINX Plus as the relying party. + +{{< see-also >}}{{< readfile file="includes/nginx-openid-repo-note.txt" markdown="true" >}}{{< /see-also >}} + + + +## Prerequisites + +The instructions assume you have the following: + +- A running Keycloak server. See the Keycloak documentation for [Getting Started](https://www.keycloak.org/guides#getting-started) and [Server](https://www.keycloak.org/guides#server) configuration instructions. +- An NGINX Plus subscription and NGINX Plus R15 or later. For installation instructions, see the [NGINX Plus Admin Guide](https://docs.nginx.com/nginx/admin-guide/installing-nginx/installing-nginx-plus/). +- The [NGINX JavaScript module](https://www.nginx.com/blog/introduction-nginscript/) (njs), required for handling the interaction between NGINX Plus and the IdP. After installing NGINX Plus, install the module with the command for your operating system. + + For Debian and Ubuntu: + + ```none + sudo apt install nginx-plus-module-njs + ``` + + For CentOS, RHEL, and Oracle Linux: + + ```shell + sudo yum install nginx-plus-module-njs + ``` + +- The following directive included in the top-level ("main") configuration context in **/etc/nginx/nginx.conf**, to load the NGINX JavaScript module: + + ```nginx + load_module modules/ngx_http_js_module.so; + ``` + + +## Configuring Keycloak + +**Note:** The following procedure reflects the Keycloak GUI at the time of publication, but the GUI is subject to change. Use this guide as a reference and adapt to the current Keycloak GUI as necessary. + +Create a Keycloak client for NGINX Plus in the Keycloak GUI: + +1. Access the Keycloak Admin Console at **http://_keycloak-server-address_:8080/auth/admin/** and log in. + +2. In the left navigation column, click **Clients**. On the **Clients** page that opens, click the **Create** button in the upper right corner. + + +3. On the **Add Client** page that opens, enter or select these values, then click the  Save  button. + + - **Client ID** – The name of the application for which you're enabling SSO (Keycloak refers to it as the “client”). Here we're using NGINX-Plus. + - **Client Protocol** – openid-connect. + + + +4. On the **NGINX Plus** page that opens, enter or select these values on the Settings tab: + + - **Access Type** – confidential + - **Valid Redirect URIs** – The URI of the NGINX Plus instance, including the port number, and ending in **/\_codexch** (in this guide it is https://my-nginx.example.com:443/_codexch) + + **Notes:** + + - For production, we strongly recommend that you use SSL/TLS (port 443). + - The port number is mandatory even when you're using the default port for HTTP (80) or HTTPS (443). + + + + +5. Click the Credentials tab and make a note of the value in the **Secret** field. You will copy it into the NGINX Plus configuration file in [Step 4 of _Configuring NGINX Plus_](#nginx-plus-variables). + + + +6. Click the Roles tab, then click the **Add Role** button in the upper right corner of the page that opens. + +7. On the **Add Role** page that opens, type a value in the **Role Name** field (here it is nginx-keycloak-role) and click the  Save  button. + + + +8. In the left navigation column, click **Users**. On the **Users** page that opens, either click the name of an existing user, or click the **Add user** button in the upper right corner to create a new user. For complete instructions, see the [Keycloak documentation](https://www.keycloak.org/docs/latest/server_admin/index.html#user-management). + + +9. On the management page for the user (here, user01), click the Role Mappings tab. On the page that opens, select NGINX-Plus on the **Client Roles** drop‑down menu. Click nginx-keycloak-role in the **Available Roles** box, then click the **Add selected** button below the box. The role then appears in the **Assigned Roles** and **Effective Roles** boxes, as shown in the screenshot. + + + + + +## Configuring NGINX Plus + +Configure NGINX Plus as the OpenID Connect relying party: + +1. Create a clone of the [nginx-openid-connect](https://github.com/nginxinc/nginx-openid-connect) GitHub repository. + + ```shell + git clone https://github.com/nginxinc/nginx-openid-connect + ``` + +2. Copy these files from the clone to **/etc/nginx/conf.d**: + + - **frontend.conf** + - **openid\_connect.js** + - **openid\_connect.server\_conf** + - **openid\_connect\_configuration.conf** + + +3. Get the URLs for the authorization endpoint, token endpoint, and JSON Web Key (JWK) file from the Keycloak configuration. Run the following `curl` command in a terminal, piping the output to the indicated `python` command to output the entire configuration in an easily readable format. We've abridged the output to show only the relevant fields. + + ```shell + $ curl https:///auth/realms/master/.well-known/openid-configuration | python -m json.tool + ... + { + "authorization_endpoint": "https:///auth/realms/master/protocol/openid-connect/auth", + ... + "jwks_uri": "https:///auth/realms/master/protocol/openid-connect/certs", + ... + "token_endpoint": "https:///auth/realms/master/protocol/openid-connect/token", + ... + } + ``` + + +4. Using your preferred text editor, open **/etc/nginx/conf.d/openid_connect_configuration.conf**. Change the "default" parameter value of each of the following [map](https://nginx.org/en/docs/http/ngx_http_map_module.html#map) directives to the specified value: + + - `map $host $oidc_authz_endpoint` – Value of `authorization_endpoint` from [Step 3](#nginx-plus-urls) (in this guide, `https:///auth/realms/master/protocol/openid-connect/auth`) + - `map $host $oidc_token_endpoint` – Value of `token_endpoint` from [Step 3](#nginx-plus-urls) (in this guide, `https:///auth/realms/master/protocol/openid-connect/token`) + - `map $host $oidc_client` – Value in the **Client ID** field from [Step 3 of _Configuring Keycloak_](#keycloak-client-id) (in this guide, `NGINX Plus`) + - `map $host $oidc_client_secret` – Value in the **Secret** field from [Step 5 of _Configuring Keycloak_](#keycloak-secret) (in this guide, ``) + - `map $host $oidc_hmac_key` – A unique, long, and secure phrase + +5. Configure the JWK file. The procedure depends on which version of NGINX Plus you are using. + + - In NGINX Plus R17 and later, NGINX Plus can read the JWK file directly from the URL reported as `jwks_uri` in [Step 3](#nginx-plus-urls). Change **/etc/nginx/conf.d/frontend.conf** as follows: + + 1. Comment out (or remove) the [auth_jwt_key_file](http://nginx.org/en/docs/http/ngx_http_auth_jwt_module.html#auth_jwt_key_file) directive. + + 2. Uncomment the [auth_jwt_key_request](http://nginx.org/en/docs/http/ngx_http_auth_jwt_module.html#auth_jwt_key_request) directive. (Its parameter, `/_jwks_uri`, refers to the value of the `$oidc_jwt_keyfile` variable, which you set in the next step.) + 3. Change the "default" parameter of the `map $host $oidc_jwt_keyfile` directive to the value reported in the `jwks_uri` field in [Step 3](#nginx-plus-urls) (in this guide, `https:///auth/realms/master/protocol/openid-connect/certs`). + + - In NGINX Plus R16 and earlier, the JWK file must be on the local disk. (You can also use this method with NGINX Plus R17 and later if you wish.) + + 1. Copy the JSON contents from the JWK file named in the `jwks_uri` field in [Step 3](#nginx-plus-urls) (in this guide, `https:///auth/realms/master/protocol/openid-connect/certs`) to a local file (for example, `/etc/nginx/my_keycloak_jwk.json`). + 2. In **/etc/nginx/conf.d/openid_connect_configuration.conf**, change the "default" parameter of the `map $host $oidc_jwt_keyfile` directive to the local file path. + +6. Confirm that the user named by the [user](http://nginx.org/en/docs/ngx_core_module.html#user) directive in the NGINX Plus configuration (in **/etc/nginx/nginx.conf** by convention) has read permission on the JWK file. + + +## Testing + +In a browser, enter the address of your NGINX Plus instance and try to log in using the credentials of a user mapped to the role for NGINX Plus (see [Step 9 of _Configuring Keycloak_](#keycloak-users)). + + + + +## Troubleshooting + +See the [**Troubleshooting**](https://github.com/nginxinc/nginx-openid-connect#troubleshooting) section at the nginx-openid-connect repository on GitHub. + +### Revision History + +- Version 2 (March 2020) – Updates to _Configuring NGINX Plus_ section +- Version 1 (November 2019) – Initial version (NGINX Plus Release 19) diff --git a/content/nginx/deployment-guides/single-sign-on/oidc-njs/okta.md b/content/nginx/deployment-guides/single-sign-on/oidc-njs/okta.md new file mode 100644 index 000000000..d777a1481 --- /dev/null +++ b/content/nginx/deployment-guides/single-sign-on/oidc-njs/okta.md @@ -0,0 +1,188 @@ +--- +description: Learn how to enable single sign-on (SSO) with Okta for applications proxied + by F5 NGINX Plus. +docs: DOCS-466 +doctypes: +- task +title: Single Sign-On with Okta and njs +toc: false +weight: 100 +--- + +{{< note >}} This guide applies to NGINX Plus [Release 15]({{< ref "nginx/releases.md#r15" >}}) and later, based on the [`nginx-openid-connect`](https://github.com/nginxinc/nginx-openid-connect) GitHub repo. Starting with NGINX Plus [Release 34]({{< ref "nginx/releases.md#r34" >}}), use the simpler solution with the [native OpenID connect module](https://nginx.org/en/docs/http/ngx_http_oidc_module.html). + +See [Single Sign-On With Okta]({{< ref "nginx/deployment-guides/single-sign-on/okta.md" >}}) for details.{{< /note >}} + +You can use NGINX Plus with Okta and OpenID Connect to enable single sign-on (SSO) for your proxied applications. By following the steps in this guide, you will learn how to set up SSO using OpenID Connect as the authentication mechanism, with Okta as the identity provider (IdP), and NGINX Plus as the relying party. + +{{< see-also >}}{{< readfile file="includes/nginx-openid-repo-note.txt" markdown="true" >}}{{< /see-also >}} + +## Prerequisites + +To complete the steps in this guide, you need the following: + +- An Okta administrator account. +- [NGINX Plus](https://www.f5.com/products/nginx/nginx-plus) with a valid subscription. +- The [NGINX JavaScript module](https://www.nginx.com/products/nginx/modules/nginx-javascript/) (`njs`) -- the `njs` module handles the interaction between NGINX Plus and Okta. +- Install `jq` on the host machine where you installed NGINX Plus. + +## Install NGINX Plus and the njs Module + +1. If you do not already have NGINX Plus installed, follow the steps in the [NGINX Plus Admin Guide](https://docs.nginx.com/nginx/admin-guide/installing-nginx/installing-nginx-plus/) to do so. +2. Install the NGINX JavaScript module by following the steps in the [`njs` installation guide](https://nginx.org/en/docs/njs/install.html). +3. Add the following directive to the top-level ("main") configuration context in the NGINX Plus configuration (`/etc/nginx/nginx.conf`) to load the `njs` module: + + ```Nginx configuration file + load_module modules/ngx_http_js_module.so; + ``` + +## Configure Okta {#okta} + +Take the steps in this section to create a new application for NGINX Plus. + +{{< note >}} This section contains images that reflect the state of the Okta web interface at the time of publication. The actual Okta GUI may differ from the examples shown here. Use this guide as a reference and adapt the instructions to suit the current Okta GUI as necessary.{{< /note >}} + +This section describes the Okta Workforce Identity SSO product. You will need administrator access to your organization in Okta to complete this task. Your experience may differ somewhat if you're using the Okta Customer Identity product. + +### Create a New Okta Web Application + + + +1. Log in to Okta at [okta.com](https:///www.okta.com). +1. Select the **Admin** button next to your username to access the Admin console. +1. On your Admin dashboard, select **Applications** in the left-hand navigation menu. +1. On the **Applications** page, select the **Create App Integration** button. +1. In the **Create a new app integration** window, define the following values, then select **Next**: + + - **Sign-in method**: OIDC - OpenID Connect + - **Application type**: Web Application + + {{< img src="/img/sso/okta/Okta-Create-New-Application-Integration.png" alt="image showing the Create a new app integration window in the Okta UI, with OIDC and Web Application options selected" width="65%" >}} + +### Set up the Web App Integration {#okta-integration} + +On the **New Web App Integration** page in the Okta web interface, fill in the following information, then select **Save**. + +{{< bootstrap-table "table table-striped table-bordered" >}} + +| Field | Desciption | Example Value | +|-------------|---------|----------| +| **App integration name** | The name of the OpenID Connect relying party. Okta refers to this as the "application". | **NGINX-Plus** | +| **Sign-in redirect URIs** | The URI of the NGINX Plus instance -- including the port number -- ending in **`/_codexch`**.
  • The port is always required, even if you use the default port for HTTP (`80`) or HTTPS (`443`).
  • The use of SSL/TLS (`443`) is strongly recommended for production environments.
| `https://my-nginx.example.com:443/_codexch` | +| **Sign-out redirect URIs** | The URI to redirect users to after logging out.
This is an optional field with a default value of `http://localhost:8080`. | We removed the default value in our example. | +| **Controlled access** | Controls who can access the application. | "Allow everyone in your organization to access"
**You should select the appropriate value for your use case.**| + +{{< /bootstrap-table >}} + +{{< img alt="Okta Create OpenID Connect Integration" src="/img/sso/okta/Okta-Create-OpenID-Connect-Integration.png" >}} + +### Get the Okta App Client Credentials {#okta-client-id-secret} + +After you finish creating your application, the Okta Application page should display. You can find the Client Credentials for your Okta Application here. + +{{< img src="/img/sso/okta/Okta-Client-Credentials.png" alt="Image showing the application landing page in Okta, which contains the Client Credentials for the application." width="65%" >}} + +{{< tip >}}If you need to find this information later, log in to your Okta admin account as [described above](#okta-login), select **Applications** in the left-hand menu, then select your application.{{< /tip >}} + +Make note of the **Client ID** and **Client secret** values for your application. You will need these when you [configure NGINX Plus](#nginx-plus). + +### Manage Access to your Okta Application {#okta-assign-applications} + +To change the users and groups that have access to your Okta Application: + +1. Log in to Okta as an Admin as [described above](#okta-login). +1. Select **Applications** in the left-hand menu, then select your application. +1. Select the **Assignments** tab for the Application. + +Here, you can manage which users in your organization are granted access to this application. + +## Set up NGINX Plus {#nginx-plus} + +Take the steps in this section to set up NGINX Plus as the OpenID Connect relying party. + +### Configure NGINX OpenID Connect {#nginx-plus-oidc-config} + +1. Clone the [nginx-openid-connect](https://github.com/nginxinc/nginx-openid-connect) GitHub repository, or download the repo files. + + ```shell + git clone https://github.com/nginxinc/nginx-openid-connect.git + ``` + +1. Copy the following files to the `/etc/nginx/conf.d` directory on the host machine where NGINX Plus is installed: + + - `frontend.conf` + - `openid_connect.js` + - `openid_connect.server_conf` + - `openid_connect_configuration.conf` + +1. Get the URLs for the authorization endpoint, token endpoint, and JSON Web Key (JWK) file from the Okta configuration. + + Run the following `curl` command in a terminal. + {{< tip>}}We recommend piping the output to `jq` to output the entire configuration in an easily readable format.{{< /tip >}} + The output in the example below is abridged to show only the relevant fields. + + ```shell + curl https://-admin.okta.com/.well-known/openid-configuration | jq + ... + { + "authorization_endpoint": "https://.okta.com/oauth2/v1/authorize", + ... + "jwks_uri": "https://.okta.com/oauth2/v1/keys", + ... + "token_endpoint": "https://.okta.com/oauth2/v1/token", + ... + } + ``` + + + +1. Add the correct values for your IdP to the OpenID Connect configuration file (`/etc/nginx/conf.d/openid_connect_configuration.conf`). + + This file contains the primary configuration for one or more IdPs in `map{}` blocks. You should modify the `map…$oidc_` blocks as appropriate to match your IdP configuration. + + - Define the `$oidc_authz_endpoint`, `$oidc_token_endpoint`, and `$oidc_jwt_keyfile` values using the information returned in the previous step. + - Change the URI defined in `map…$oidc_logout_redirect` to the URI of a resource (for example, your home page) that should be displayed after a client requests the `/logout` location. + - Set a unique, long, and secure phrase for `$oidc_hmac_key` to ensure nonce values are unpredictable. + +### Set up JSON Web Key Authorization {#nginx-plus-jwk-config} + +NGINX Plus can read the JWK file directly from the URL reported as `jwks_uri` in the output of the `curl` command you ran in the [previous section](#nginx-plus-oidc-config). + +{{< note >}} +If you are using NGINX Plus R16 or earlier, refer to [Set up JWK Authorization using a local file](#nginx-plus-jwk-auth-local). +{{< /note >}} + +Take the following steps to set up NGINX Plus to access the JWK file by using a URI. + +1. In the `/etc/nginx/conf.d/frontend.conf` file, remove (or comment out) the [auth_jwt_key_file](http://nginx.org/en/docs/http/ngx_http_auth_jwt_module.html#auth_jwt_key_file) directive. +1. Uncomment the [auth_jwt_key_request](http://nginx.org/en/docs/http/ngx_http_auth_jwt_module.html#auth_jwt_key_request) directive. + + The parameter `/_jwks_uri` refers to the value of the `$oidc_jwt_keyfile` variable, which you already set in the OpenID Connect configuration file (`/etc/nginx/conf.d/openid_connect_configuration.conf`). + +#### Set up JWK Authorization using a Local File {#nginx-plus-jwk-auth-local} + +In NGINX Plus R16 and earlier, NGINX Plus cannot access the JWK file via the URI. Instead, the JWK file must be on the local disk. + +Take the steps below to set up JWK authorization using a local file: + +1. Copy the JSON contents from the JWK file named in the `jwks_uri` field to a local file. For example, `/etc/nginx/my_okta_jwk.json` +1. In `/etc/nginx/conf.d/frontend.conf`, change the second parameter of the `set $oidc_jwt_keyfile` directive to the local file path of the JWK file. +1. Confirm that the user named by the [user](http://nginx.org/en/docs/ngx_core_module.html#user) directive in the NGINX Plus configuration -- usually found in `/etc/nginx/nginx.conf` -- has read permission on the JWK file. + +## Test Your Setup + +1. In a browser, enter the address of your NGINX Plus instance. You should be directed to the okta login page, as shown in the example below. + {{< img src="img/sso/okta/Okta-login-window.png" >}} +1. Try to log in using the credentials of a user who is part of your organization. + +{{}}If you restricted access to a group of users, be sure to select a user who has access to the application.{{}} + +## Troubleshooting + +Refer to the [Troubleshooting](https://github.com/nginxinc/nginx-openid-connect#troubleshooting) section in the `nginx-openid-connect` repository on GitHub. + +### Revision History + +- Version 3 (March 2022) – Full edit incorporating updates to _Configuring Okta_ and _Configuring NGINX Plus_ +- Version 2 (March 2020) – Updates to _Configuring NGINX Plus_ section +- Version 1 (April 2019) – Initial version (NGINX Plus Release 17) diff --git a/content/nginx/deployment-guides/single-sign-on/oidc-njs/onelogin.md b/content/nginx/deployment-guides/single-sign-on/oidc-njs/onelogin.md new file mode 100644 index 000000000..dbe3b4d4f --- /dev/null +++ b/content/nginx/deployment-guides/single-sign-on/oidc-njs/onelogin.md @@ -0,0 +1,159 @@ +--- +description: Learn how to enable single sign-on (SSO) with [OneLogin](https://www.onelogin.com/) + for applications proxied by F5 NGINX Plus. +docs: DOCS-467 +doctypes: +- tutorial +tags: +- docs +title: Single Sign-On with OneLogin and njs +toc: false +weight: 100 +--- + +{{< note >}} This guide applies to NGINX Plus [Release 15]({{< ref "nginx/releases.md#r15" >}}) and later, based on the [`nginx-openid-connect`](https://github.com/nginxinc/nginx-openid-connect) GitHub repo. Starting with NGINX Plus [Release 34]({{< ref "nginx/releases.md#r34" >}}), use the simpler solution with the [native OpenID connect module](https://nginx.org/en/docs/http/ngx_http_oidc_module.html). + +See [Single Sign-On With OneLogin]({{< ref "nginx/deployment-guides/single-sign-on/onelogin.md" >}}) for details.{{< /note >}} + +You can use NGINX Plus with [OneLogin](https://www.onelogin.com/) and the OpenID Connect protocol to enable single sign-on (SSO) for your proxied applications. By following the steps in this guide, you will learn how to set up SSO using OpenID Connect as the authentication mechanism, with OneLogin as the identity provider (IdP) and NGINX Plus as the relying party. + +{{< see-also >}}{{< readfile file="includes/nginx-openid-repo-note.txt" markdown="true" >}}{{< /see-also >}} + +## Prerequisites + +To complete the steps in this guide, you need the following: + +- A OneLogin tenant with administrator privileges. +- [NGINX Plus](https://www.f5.com/products/nginx/nginx-plus) with a valid subscription. +- The [NGINX JavaScript module](https://www.nginx.com/products/nginx/modules/nginx-javascript/) (`njs`) -- the `njs` module handles the interaction between NGINX Plus and OneLogin identity provider (IdP). + +## Install NGINX Plus and the njs Module {#install-nginx-plus-njs} + +1. If you do not already have NGINX Plus installed, follow the [NGINX Plus Admin Guide](https://docs.nginx.com/nginx/admin-guide/installing-nginx/installing-nginx-plus/) steps to do so. +2. Install the NGINX JavaScript module by following the steps in the [`njs` installation guide](https://nginx.org/en/docs/njs/install.html). +3. Add the following directive to the top-level ("main") configuration context in the NGINX Plus configuration (`/etc/nginx/nginx.conf`) to load the `njs` module: + + ```Nginx configuration file + load_module modules/ngx_http_js_module.so; + ``` + +## Configure OneLogin {#config-onelogin} + +**Note:** The following procedure reflects the OneLogin GUI at the time of publication, but the GUI is subject to change. Use this guide as a reference and adapt to the current OneLogin GUI as necessary. + +Create a new application for NGINX Plus in the OneLogin GUI: + +1. Log in to your OneLogin account at **https://**_domain_**.onelogin.com**, where _domain_ is the domain you chose when you created your account. + +2. Click  Applications  in the title bar and then click the  Add App  button in the upper right corner of the window that opens. + + + +3. On the **Find Applications** page that opens, type OpenID Connect in the search box. Click on the **OpenID Connect (OIDC)** row that appears. + + + +4. On the **Add OpenId Connect (OIDC)** page that opens, change the value in the **Display Name** field to NGINX Plus and click the  Save  button. + + + +5. When the save completes, a new set of choices appears in the left navigation bar. Click **Configuration**. In the **Redirect URI's** field, type the URI of the NGINX Plus instance including the port number, and ending in **/\_codexch** (in this guide it is https://my-nginx.example.com:443/_codexch). Then click the  Save  button. + + **Notes:** + + - For production, we strongly recommend that you use SSL/TLS (port 443). + - The port number is mandatory even when you're using the default port for HTTP (80) or HTTPS (443). + + + + +6. When the save completes, click **SSO** in the left navigation bar. Click Show client secret below the **Client Secret** field. Record the values in the **Client ID** and **Client Secret** fields. You will add them to the NGINX Plus configuration in [Step 4 of _Configuring NGINX Plus_](#nginx-plus-variables). + + + + +7. Assign users to the application (in this guide, NGINX Plus) to enable them to access it for SSO. OneLogin recommends using [roles](https://onelogin.service-now.com/kb_view_customer.do?sysparm_article=KB0010606) for this purpose. You can access the **Roles** page under  Users  in the title bar. + + + + +## Set up NGINX Plus + +Take the steps in this section to set up NGINX Plus as the OpenID Connect Client. + +### Configure NGINX OpenID Connect {#nginx-plus-oidc-config} + +1. Clone the [nginx-openid-connect](https://github.com/nginxinc/nginx-openid-connect) GitHub repository, or download the repository files. + + ```shell + git clone https://github.com/nginxinc/nginx-openid-connect.git + ``` + +1. Run the _configure.sh_ script to update the NGINX configuration files with the values for your OneLogin application. + + For example: + + ```bash + ./nginx-openid-connect/configure.sh \ + --auth_jwt_key request \ + --client_id 168d5600-9224-... \ + --client_secret c9210a67d09e85... \ + https://.onelogin.com/oidc/2/.well-known/openid-configuration + ``` + +2. In the `frontend.conf` file, update the **my_backend** upstream with the address of the application that you want to add OIDC authorization to. + + For example: + + ```Nginx configuration file + upstream my_backend { + zone my_backend 64k; + server my-backend-app.com:80; + } + ``` + +3. In the _openid_connect.server_conf_ file, add the [`proxy_set_header`](https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_set_header) directive to the `/_jwks_uri` and `/_token` locations to `Accept-Encoding "gzip"`, as shown below. + + ```Nginx configuration file + ... + location = /_jwks_uri { + ... + proxy_set_header Accept-Encoding "gzip" + } + ... + location = /_token { + ... + proxy_set_header Accept-Encoding "gzip" + } + ... + ``` + +4. Copy the following files to the _/etc/nginx/conf.d_ directory on the host machine where NGINX Plus is installed: + + - `frontend.conf` + - `openid_connect.js` + - `openid_connect.server_conf` + - `openid_connect_configuration.conf` + +5. Reload the NGINX configuration: + + ```bash + sudo nginx -s reload + ``` + +## Test Your Setup + +In a browser, enter the address of your NGINX Plus instance and try to log in using the credentials of a user assigned to the application (see [Step 7 of _Configuring OneLogin_](#onelogin-roles)). + + + + +## Troubleshooting + +Refer to the [Troubleshooting](https://github.com/nginxinc/nginx-openid-connect#troubleshooting) section in the `nginx-openid-connect` repository on GitHub. + +### Revision History + +- Version 3 (May 2022) - Updates OneLogin's OpenId Connect API endpoints from version 1 to version 2 +- Version 2 (March 2020) – Updates to _Configuring NGINX Plus_ section +- Version 1 (July 2019) – Initial version (NGINX Plus Release 18) diff --git a/content/nginx/deployment-guides/single-sign-on/oidc-njs/ping-identity.md b/content/nginx/deployment-guides/single-sign-on/oidc-njs/ping-identity.md new file mode 100644 index 000000000..9acf0068c --- /dev/null +++ b/content/nginx/deployment-guides/single-sign-on/oidc-njs/ping-identity.md @@ -0,0 +1,197 @@ +--- +description: Enable OpenID Connect-based single-sign for applications proxied by NGINX + Plus, using Ping Identity as the identity provider (IdP). +docs: DOCS-468 +doctypes: +- task +title: Single Sign-On with Ping Identity and njs +toc: false +weight: 100 +--- + +{{< note >}} This guide applies to NGINX Plus [Release 15]({{< ref "nginx/releases.md#r15" >}}) and later, based on the [`nginx-openid-connect`](https://github.com/nginxinc/nginx-openid-connect) GitHub repo. Starting with NGINX Plus [Release 34]({{< ref "nginx/releases.md#r34" >}}), use the simpler solution with the [native OpenID connect module](https://nginx.org/en/docs/http/ngx_http_oidc_module.html). + +See [Single Sign-On With Ping Identity]({{< ref "nginx/deployment-guides/single-sign-on/ping-identity.md" >}}) for details.{{< /note >}} + +This guide explains how to enable single sign-on (SSO) for applications being proxied by F5 NGINX Plus. The solution uses OpenID Connect as the authentication mechanism, with Ping Identity as the identity provider (IdP) and NGINX Plus as the relying party. + +The instructions in this document apply to both Ping Identity's on‑premises and cloud products, PingFederate and PingOne for Enterprise. + +{{< see-also >}}{{< readfile file="includes/nginx-openid-repo-note.txt" markdown="true" >}}{{< /see-also >}} + + +## Prerequisites + +The instructions assume you have the following: + +- A running deployment of PingFederate or PingOne for Enterprise, and a Ping Identity account. For installation and configuration instructions, see the documentation for [PingFederate](https://docs.pingidentity.com/bundle/pingfederate-93/page/tau1564002955783.html) or [PingOne for Enterprise](https://docs.pingidentity.com/bundle/pingone/page/fjn1564020491958-1.html). +- An NGINX Plus subscription and NGINX Plus R15 or later. For installation instructions, see the [NGINX Plus Admin Guide](https://docs.nginx.com/nginx/admin-guide/installing-nginx/installing-nginx-plus/). +- The NGINX JavaScript module (njs), required for handling the interaction between NGINX Plus and the IdP. After installing NGINX Plus, install the module with the command for your operating system. + + For Debian and Ubuntu: + + ```none + sudo apt install nginx-plus-module-njs + ``` + + For CentOS, RHEL, and Oracle Linux: + + ```shell + sudo yum install nginx-plus-module-njs + ``` + +- The following directive included in the top-level ("main") configuration context in **/etc/nginx/nginx.conf**, to load the NGINX JavaScript module: + + ```nginx + load_module modules/ngx_http_js_module.so; + ``` + + +## Configuring PingFederate or PingOne for Enterprise + +**Note:** This guide uses the GUI provided with PingOne for Enterprise. It reflects the GUI at the time of initial publication, but the GUI is subject to change. The PingFederate user interface might also differ. Use this guide as a reference and adapt as necessary for the UI you are using. + +Create a new application for NGINX Plus: + +1. Log in to your Ping Identity account. The administrative dashboard opens automatically. In this guide, we show the PingOne for Enterprise dashboard, and for brevity refer simply to ”PingOne”. + +2. Click  APPLICATIONS  in the title bar, and on the **My Applications** page that opens, click **OIDC** and then the + Add Application button. + + + +3. The Add OIDC Application window pops up. Click the ADVANCED CONFIGURATION box, and then the  Next  button. + + + +4. In section 1 (PROVIDE DETAILS ABOUT YOUR APPLICATION), type a name in the **APPLICATION NAME** field and a short description in the **SHORT DESCRIPTION** field. Here, we're using nginx-plus-application and NGINX Plus. Choose a value from the **CATEGORY** drop‑down menu; here we’re using Information Technology. You can also add an icon if you wish. Click the  Next  button. + + + +5. In section 2 (AUTHORIZATION SETTINGS), perform these steps: + + 1. Under **GRANTS**, click both Authorization Code and Implicit.
+ 2. Under **CREDENTIALS**, click the + Add Secret button. PingOne creates a client secret and opens the **CLIENT SECRETS** field to display it, as shown in the screenshot. To see the actual value of the secret, click the eye icon.
+ 3. Click the  Next  button. + + + +6. In section 3 (SSO FLOW AND AUTHENTICATION SETTINGS): + + 1. In the START SSO URL field, type the URL where users access your application. Here we’re using https://example.com. + 2. In the **REDIRECT URIS** field, type the URI of the NGINX Plus instance including the port number, and ending in **/\_codexch**. Here we’re using https://my-nginx-plus.example.com:443/\_codexch (the full value is not visible in the screenshot). + + **Notes:** + + - For production, we strongly recommend that you use SSL/TLS (port 443). + - The port number is mandatory even when you're using the default port for HTTP (80) or HTTPS (443). + + + +7. In section 4 (DEFAULT USER PROFILE ATTRIBUTE CONTRACT), optionally add attributes to the required sub and idpid attributes, by clicking the + Add Attribute button. We’re not adding any in this example. When finished, click the  Next  button. + + + +8. In section 5 (CONNECT SCOPES), click the circled plus-sign on the OpenID Profile (profile) and OpenID Profile Email (email) scopes in the LIST OF SCOPES column. They are moved to the **CONNECTED SCOPES** column, as shown in the screenshot. Click the  Next  button. + + + +9. In section 6 (ATTRIBUTE MAPPING), map attributes from your identity repository to the claims available to the application. The one attribute you must map is **sub**, and here we have selected the value Email from the drop‑down menu (the screenshot is abridged for brevity). + + + + +10. In section 7 (GROUP ACCESS), select the groups that will have access to the application, by clicking the circled plus-sign on the corresponding boxes in the **AVAILABLE GROUPS** column. The boxes move to the **ADDED GROUPS** column. As shown in the screenshot we have selected the two default groups, Domain Administrators@directory and Users@directory. + + Click the  Done  button. + + + +11. You are returned to the **My Applications** window, which now includes a row for nginx-plus-application. Click the toggle switch at the right end of the row to the “on” position, as shown in the screenshot. Then click the “expand” icon at the end of the row, to display the application’s details. + + + + +12. On the page that opens, make note of the values in the following fields on the **Details** tab. You will add them to the NGINX Plus configuration in [Step 4 of _Configuring NGINX Plus_](#nginx-plus-variables). + + - **CLIENT ID** (in the screenshot, 28823604-83c5-4608-88da-c73fff9c607a) + - **CLIENT SECRETS** (in the screenshot, 7GMKILBofxb...); click on the eye icon to view the actual value + + + + +## Configuring NGINX Plus + +Configure NGINX Plus as the OpenID Connect relying party: + +1. Create a clone of the [nginx-openid-connect](https://github.com/nginxinc/nginx-openid-connect) GitHub repository. + + ```shell + git clone https://github.com/nginxinc/nginx-openid-connect + ``` + +2. Copy these files from the clone to **/etc/nginx/conf.d**: + + - **frontend.conf** + - **openid\_connect.js** + - **openid\_connect.server\_conf** + + +3. Get the URLs for the authorization endpoint, token endpoint, and JSON Web Key (JWK) file from the Ping Identity configuration. Run the following `curl` command in a terminal, piping the output to the indicated `python` command to output the entire configuration in an easily readable format. We've abridged the output to show only the relevant fields. + + The `` variable is the value in the **CLIENT ID** field that you noted in [Step 12 of _Configuring PingFederate or PingOne for Enterprise_](#ping-client-id-secrets). + + **Note:** This `curl` command is appropriate for Ping One for Enterprise. For PingFederate, you might need to replace `sso.connect.pingidentity.com` with the IP address of your local PingFederate server. + + ```shell + $ curl sso.connect.pingidentity.com//.well-known/openid-configuration | python -m json.tool + ... + { + "authorization_endpoint": "https://sso.connect.pingidentity.com/sso/as/authorization.oauth2", + ... + "jwks_uri": "https://sso.connect.pingidentity.com/sso/as/jwks", + ... + "token_endpoint": "https://sso.connect.pingidentity.com/sso/as/token.oauth2", + ... + } + ``` + + +4. In your preferred text editor, open **/etc/nginx/conf.d/frontend.conf**. Change the second parameter of each of the following [set](http://nginx.org/en/docs/http/ngx_http_rewrite_module.html#set) directives to the specified value: + + - `set $oidc_authz_endpoint` – Value of `authorization_endpoint` from [Step 3](#nginx-plus-urls) (in this guide, `https://sso.connect.pingidentity.com/sso/as/authorization.oauth2`) + - `set $oidc_token_endpoint` – Value of `token_endpoint` from [Step 3](#nginx-plus-urls) (in this guide, `https://sso.connect.pingidentity.com/sso/as/token.oauth2`) + - `set $oidc_client` – Value in the **CLIENT ID** field in [Step 12 of _Configuring PingFederate or PingOne for Enterprise_](#ping-client-id-secrets) (in this guide, `28823604-83c5-4608-88da-c73fff9c607a`) + - `set $oidc_client_secret` – Value in the **CLIENT SECRETS** field in [Step 12 of _Configuring PingFederate or PingOne for Enterprise_](#ping-client-id-secrets) (in this guide, `7GMKILBofxb...`) + - `set $oidc_hmac_key` – A unique, long, and secure phrase + +5. Configure the JWK file. The procedure depends on which version of NGINX Plus you are using. + + - In NGINX Plus R17 and later, NGINX Plus can read the JWK file directly from the URL reported as `jwks_uri` in [Step 3](#nginx-plus-urls). Change **/etc/nginx/conf.d/frontend.conf** as follows: + + 1. Comment out (or remove) the [auth_jwt_key_file](http://nginx.org/en/docs/http/ngx_http_auth_jwt_module.html#auth_jwt_key_file) directive. + 2. Uncomment the [auth_jwt_key_request](http://nginx.org/en/docs/http/ngx_http_auth_jwt_module.html#auth_jwt_key_request) directive. (Its parameter, `/_jwks_uri`, refers to the value of the `$oidc_jwt_keyfile` variable, which you set in the next step.) + 3. Change the second parameter of the `set $oidc_jwt_keyfile` directive to the value reported in the `jwks_uri` field in [Step 3](#nginx-plus-urls) (in this guide, `https://sso.connect.pingidentity.com/sso/as/jwks`). + + - In NGINX Plus R16 and earlier, the JWK file must be on the local disk. (You can also use this method with NGINX Plus R17 and later if you wish.) + + 1. Copy the JSON contents from the JWK file named in the `jwks_uri` field in [Step 3](#nginx-plus-urls) (in this guide, `https://sso.connect.pingidentity.com/sso/as/jwks`) to a local file (for example, `/etc/nginx/my_ping_identity_jwk.json`). + 2. In **/etc/nginx/conf.d/frontend.conf**, change the second parameter of the `set $oidc_jwt_keyfile` directive to the local file path. + +6. Confirm that the user named by the [user](http://nginx.org/en/docs/ngx_core_module.html#user) directive in the NGINX Plus configuration (in **/etc/nginx/nginx.conf** by convention) has read permission on the JWK file. + + +## Testing + +In a browser, enter the address of your NGINX Plus instance and try to log in using the credentials of a user assigned to the application (see [Step 10 of _PingFederate or PingOne for Enterprise_](#ping-group-access)). + + + + +## Troubleshooting + +See the [**Troubleshooting**](https://github.com/nginxinc/nginx-openid-connect#troubleshooting) section at the nginx-openid-connect repository on GitHub. + +### Revision History + +- Version 2 (March 2020) – Updates to _Configuring NGINX Plus_ section +- Version 1 (January 2020) – Initial version (NGINX Plus Release 20) diff --git a/content/nginx/deployment-guides/single-sign-on/okta.md b/content/nginx/deployment-guides/single-sign-on/okta.md index 2b5af9cc6..24720d14e 100644 --- a/content/nginx/deployment-guides/single-sign-on/okta.md +++ b/content/nginx/deployment-guides/single-sign-on/okta.md @@ -1,189 +1,311 @@ --- -description: Learn how to enable single sign-on (SSO) with Okta for applications proxied - by F5 NGINX Plus. -docs: DOCS-466 +description: Enable OpenID Connect-based single sign-on (SSO) for applications proxied by NGINX Plus, using Okta as the identity provider (IdP). +doctypes: +- task title: Single Sign-On with Okta toc: true -weight: 100 -type: -- how-to +weight: 700 --- -
+This guide explains how to enable single sign-on (SSO) for applications being proxied by F5 NGINX Plus. The solution uses OpenID Connect as the authentication mechanism, with [Okta](https://www.okta.com/) as the Identity Provider (IdP), and NGINX Plus as the Relying Party, or OIDC client application that verifies user identity. -This documentation applies to F5 NGINX Plus R15 and later. -
+{{< note >}} This guide applies to [NGINX Plus Release 34]({{< ref "nginx/releases.md#r34" >}}) and later. In earlier versions, NGINX Plus relied on an [njs-based solution](#legacy-njs-guide), which required NGINX JavaScript files, key-value stores, and advanced OpenID Connect logic. In the latest NGINX Plus version, the new [OpenID Connect module](https://nginx.org/en/docs/http/ngx_http_oidc_module.html) simplifies this process to just a few directives.{{< /note >}} -You can use NGINX Plus with Okta and OpenID Connect to enable single sign-on (SSO) for your proxied applications. By following the steps in this guide, you will learn how to set up SSO using OpenID Connect as the authentication mechanism, with Okta as the identity provider (IdP), and NGINX Plus as the relying party. +## Prerequisites -{{< see-also >}}{{< include "nginx-plus/nginx-openid-repo-note.txt" >}}{{< /see-also >}} +- An [Okta](https://www.okta.com/) administrator account with privileges to create and manage applications. -## Prerequisites +- An NGINX Plus [subscription](https://www.f5.com/products/nginx/nginx-plus) and NGINX Plus [Release 34](({{< ref "nginx/releases.md#r34" >}})) or later. For installation instructions, see [Installing NGINX Plus](https://docs.nginx.com/nginx/admin-guide/installing-nginx/installing-nginx-plus/). -To complete the steps in this guide, you need the following: +- A domain name pointing to your NGINX Plus instance, for example, `demo.example.com`. -- An Okta administrator account. -- [NGINX Plus](https://www.f5.com/products/nginx/nginx-plus) with a valid subscription. -- The [NGINX JavaScript module](https://www.nginx.com/products/nginx/modules/nginx-javascript/) (`njs`) -- the `njs` module handles the interaction between NGINX Plus and Okta. -- Install `jq` on the host machine where you installed NGINX Plus. -## Install NGINX Plus and the njs Module +## Configure Okta {#okta-setup} -1. If you do not already have NGINX Plus installed, follow the steps in the [NGINX Plus Admin Guide](https://docs.nginx.com/nginx/admin-guide/installing-nginx/installing-nginx-plus/) to do so. -2. Install the NGINX JavaScript module by following the steps in the [`njs` installation guide](https://nginx.org/en/docs/njs/install.html). -3. Add the following directive to the top-level ("main") configuration context in the NGINX Plus configuration (`/etc/nginx/nginx.conf`) to load the `njs` module: +In Okta, register a new application for NGINX Plus as the OIDC client to obtain the Client ID, Client Secret, and required OIDC endpoints. - ```Nginx configuration file - load_module modules/ngx_http_js_module.so; - ``` +1. Log in to your Okta admin console. -## Configure Okta {#okta} +2. In the Admin Console, go to **Applications** > **Applications**. -Take the steps in this section to create a new application for NGINX Plus. +3. Select **Create App Integration**. -{{< note >}} This section contains images that reflect the state of the Okta web interface at the time of publication. The actual Okta GUI may differ from the examples shown here. Use this guide as a reference and adapt the instructions to suit the current Okta GUI as necessary.{{< /note >}} +4. In **Create a new app integration**, select: -This section describes the Okta Workforce Identity SSO product. You will need administrator access to your organization in Okta to complete this task. Your experience may differ somewhat if you're using the Okta Customer Identity product. + - **Sign-in method**: `OIDC - OpenID Connect`. -### Create a New Okta Web Application + - **Application type**: `Web Application`. - + - Select **Next**. -1. Log in to Okta at [okta.com](https:///www.okta.com). -1. Select the **Admin** button next to your username to access the Admin console. -1. On your Admin dashboard, select **Applications** in the left-hand navigation menu. -1. On the **Applications** page, select the **Create App Integration** button. -1. In the **Create a new app integration** window, define the following values, then select **Next**: +5. In **New Web App Integration**: - - **Sign-in method**: OIDC - OpenID Connect - - **Application type**: Web Application + - Enter the **Name** for your new application, for example, **Nginx Demo App**. - {{< img src="/img/sso/okta/Okta-Create-New-Application-Integration.png" alt="image showing the Create a new app integration window in the Okta UI, with OIDC and Web Application options selected" width="65%" >}} + - Add a URI for the OIDC callback in **Sign-in redirect URIs**, for example, `https://demo.example.com/oidc_callback`. -### Set up the Web App Integration {#okta-integration} + - Select **Save**. -On the **New Web App Integration** page in the Okta web interface, fill in the following information, then select **Save**. +6. In **Applications**, select **Nginx Demo App**. -{{< bootstrap-table "table table-striped table-bordered" >}} +7. In the **General** tab: -| Field | Desciption | Example Value | -|-------------|---------|----------| -| **App integration name** | The name of the OpenID Connect relying party. Okta refers to this as the "application". | **NGINX-Plus** | -| **Sign-in redirect URIs** | The URI of the NGINX Plus instance -- including the port number -- ending in **`/_codexch`**.
  • The port is always required, even if you use the default port for HTTP (`80`) or HTTPS (`443`).
  • The use of SSL/TLS (`443`) is strongly recommended for production environments.
| `https://my-nginx.example.com:443/_codexch` | -| **Sign-out redirect URIs** | The URI to redirect users to after logging out.
This is an optional field with a default value of `http://localhost:8080`. | We removed the default value in our example. | -| **Controlled access** | Controls who can access the application. | "Allow everyone in your organization to access"
**You should select the appropriate value for your use case.**| + - Copy the **Client ID**. You will need it later when configuring NGINX Plus. -{{< /bootstrap-table >}} + - Copy the **Client secret**. You will need it later when configuring NGINX Plus. -{{< img alt="Okta Create OpenID Connect Integration" src="/img/sso/okta/Okta-Create-OpenID-Connect-Integration.png" >}} +8. In the **Sign On** tab: -### Get the Okta App Client Credentials {#okta-client-id-secret} + - Copy the **Okta Issuer (Authorization Server)**, for example: -After you finish creating your application, the Okta Application page should display. You can find the Client Credentials for your Okta Application here. + `https://dev-123456.oktapreview.com/oauth2/default` -{{< img src="/img/sso/okta/Okta-Client-Credentials.png" alt="Image showing the application landing page in Okta, which contains the Client Credentials for the application." width="65%" >}} + You will need it later when configuring NGINX Plus. -{{< tip >}}If you need to find this information later, log in to your Okta admin account as [described above](#okta-login), select **Applications** in the left-hand menu, then select your application.{{< /tip >}} +{{< note >}} You will need the values of **Client ID**, **Client Secret**, and **Issuer** in the next steps. {{< /note >}} -Make note of the **Client ID** and **Client secret** values for your application. You will need these when you [configure NGINX Plus](#nginx-plus). +### Assign Users or Groups -### Manage Access to your Okta Application {#okta-assign-applications} +By default, Okta might limit application access to certain users or groups. To add or remove users in Okta: -To change the users and groups that have access to your Okta Application: +1. Log in to your Okta admin console. -1. Log in to Okta as an Admin as [described above](#okta-login). -1. Select **Applications** in the left-hand menu, then select your application. -1. Select the **Assignments** tab for the Application. +2. In **Applications**, choose **Nginx Demo App**. -Here, you can manage which users in your organization are granted access to this application. +3. Go to **Assignments**. -## Set up NGINX Plus {#nginx-plus} +4. Add or remove users and groups that can access this application. -Take the steps in this section to set up NGINX Plus as the OpenID Connect relying party. -### Configure NGINX OpenID Connect {#nginx-plus-oidc-config} +## Set up NGINX Plus {#nginx-plus-setup} -1. Clone the [nginx-openid-connect](https://github.com/nginxinc/nginx-openid-connect) GitHub repository, or download the repo files. +With Okta configured, you can enable OIDC on NGINX Plus. NGINX Plus serves as the Rely Party (RP) application — a client service that verifies user identity. - ```shell - git clone https://github.com/nginxinc/nginx-openid-connect.git - ``` +1. Ensure that you are using the latest version of NGINX Plus by running the `nginx -v` command in a terminal: -1. Copy the following files to the `/etc/nginx/conf.d` directory on the host machine where NGINX Plus is installed: + ```shell + nginx -v + ``` + The output should match NGINX Plus Release 34 or later: + + ```none + nginx version: nginx/1.27.4 (nginx-plus-r34) + ``` - - `frontend.conf` - - `openid_connect.js` - - `openid_connect.server_conf` - - `openid_connect_configuration.conf` +2. Ensure that you have the values of the **Client ID**, **Client Secret**, and **Issuer** obtained during [Okta Configuration](#okta-setup). -1. Get the URLs for the authorization endpoint, token endpoint, and JSON Web Key (JWK) file from the Okta configuration. +3. In your preferred text editor, open the NGINX configuration file (`/etc/nginx/nginx.conf` for Linux or `/usr/local/etc/nginx/nginx.conf` for FreeBSD). - Run the following `curl` command in a terminal. - {{< tip>}}We recommend piping the output to `jq` to output the entire configuration in an easily readable format.{{< /tip >}} - The output in the example below is abridged to show only the relevant fields. +4. In the [`http {}`](https://nginx.org/en/docs/http/ngx_http_core_module.html#http) context, make sure your public DNS resolver is specified with the [`resolver`](https://nginx.org/en/docs/http/ngx_http_core_module.html#resolver) directive: By default, NGINX Plus re‑resolves DNS records at the frequency specified by time‑to‑live (TTL) in the record, but you can override the TTL value with the `valid` parameter: - ```shell - curl https://-admin.okta.com/.well-known/openid-configuration | jq - ... - { - "authorization_endpoint": "https://.okta.com/oauth2/v1/authorize", - ... - "jwks_uri": "https://.okta.com/oauth2/v1/keys", - ... - "token_endpoint": "https://.okta.com/oauth2/v1/token", - ... + ```nginx + http { + resolver 10.0.0.1 ipv4=on valid=300s; + + # ... } ``` - + +5. In the [`http {}`](https://nginx.org/en/docs/http/ngx_http_core_module.html#http) context, define the Okta provider named `okta` by specifying the [`oidc_provider {}`](https://nginx.org/en/docs/http/ngx_http_oidc_module.html#oidc_provider) context: -1. Add the correct values for your IdP to the OpenID Connect configuration file (`/etc/nginx/conf.d/openid_connect_configuration.conf`). + ```nginx + http { + resolver 10.0.0.1 ipv4=on valid=300s; - This file contains the primary configuration for one or more IdPs in `map{}` blocks. You should modify the `map…$oidc_` blocks as appropriate to match your IdP configuration. + oidc_provider okta { - - Define the `$oidc_authz_endpoint`, `$oidc_token_endpoint`, and `$oidc_jwt_keyfile` values using the information returned in the previous step. - - Change the URI defined in `map…$oidc_logout_redirect` to the URI of a resource (for example, your home page) that should be displayed after a client requests the `/logout` location. - - Set a unique, long, and secure phrase for `$oidc_hmac_key` to ensure nonce values are unpredictable. + # ... -### Set up JSON Web Key Authorization {#nginx-plus-jwk-config} + } + # ... + } + ``` -NGINX Plus can read the JWK file directly from the URL reported as `jwks_uri` in the output of the `curl` command you ran in the [previous section](#nginx-plus-oidc-config). +6. In the [`oidc_provider {}`](https://nginx.org/en/docs/http/ngx_http_oidc_module.html#oidc_provider) context, specify: -{{< note >}} -If you are using NGINX Plus R16 or earlier, refer to [Set up JWK Authorization using a local file](#nginx-plus-jwk-auth-local). -{{< /note >}} + - your actual Okta **Client ID** obtained in [Okta Configuration](#okta-setup) with the [`client_id`](https://nginx.org/en/docs/http/ngx_http_oidc_module.html#client_id) directive -Take the following steps to set up NGINX Plus to access the JWK file by using a URI. + - your **Client Secret** obtained in [Okta Configuration](#okta-setup) with the [`client_secret`](https://nginx.org/en/docs/http/ngx_http_oidc_module.html#client_secret) directive -1. In the `/etc/nginx/conf.d/frontend.conf` file, remove (or comment out) the [auth_jwt_key_file](http://nginx.org/en/docs/http/ngx_http_auth_jwt_module.html#auth_jwt_key_file) directive. -1. Uncomment the [auth_jwt_key_request](http://nginx.org/en/docs/http/ngx_http_auth_jwt_module.html#auth_jwt_key_request) directive. + - the **Issuer** URL obtained in [Okta Configuration](#okta-setup) with the [`issuer`](https://nginx.org/en/docs/http/ngx_http_oidc_module.html#client_secret) directive - The parameter `/_jwks_uri` refers to the value of the `$oidc_jwt_keyfile` variable, which you already set in the OpenID Connect configuration file (`/etc/nginx/conf.d/openid_connect_configuration.conf`). + The `issuer` is typically your Okta OIDC URL: -#### Set up JWK Authorization using a Local File {#nginx-plus-jwk-auth-local} + `https://dev-123456.okta.com/oauth2/default`. -In NGINX Plus R16 and earlier, NGINX Plus cannot access the JWK file via the URI. Instead, the JWK file must be on the local disk. + - **Important:** All interaction with the IdP is secured exclusively over SSL/TLS, so NGINX must trust the certificate presented by the IdP. By default, this trust is validated against your system’s CA bundle (the default CA store for your Linux or FreeBSD distribution). If the IdP’s certificate is not included in the system CA bundle, you can explicitly specify a trusted certificate or chain with the [`ssl_trusted_certificate`](https://nginx.org/en/docs/http/ngx_http_oidc_module.html#ssl_trusted_certificate) directive so that NGINX can validate and trust the IdP’s certificate. -Take the steps below to set up JWK authorization using a local file: + ```nginx + http { + resolver 10.0.0.1 ipv4=on valid=300s; -1. Copy the JSON contents from the JWK file named in the `jwks_uri` field to a local file. For example, `/etc/nginx/my_okta_jwk.json` -1. In `/etc/nginx/conf.d/frontend.conf`, change the second parameter of the `set $oidc_jwt_keyfile` directive to the local file path of the JWK file. -1. Confirm that the user named by the [user](http://nginx.org/en/docs/ngx_core_module.html#user) directive in the NGINX Plus configuration -- usually found in `/etc/nginx/nginx.conf` -- has read permission on the JWK file. + oidc_provider okta { + issuer https://dev-123456.okta.com/oauth2/default; + client_id ; + client_secret ; + } -## Test Your Setup + # ... + } + ``` + +7. Make sure you have configured a [server](https://nginx.org/en/docs/http/ngx_http_core_module.html#server) that corresponds to `demo.example.com`, and there is a [location](https://nginx.org/en/docs/http/ngx_http_core_module.html#location) that [points](https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_pass) to your application (see [Step 10](#oidc_app)) at `http://127.0.0.1:8080` that is going to be OIDC-protected: + + ```nginx + http { + # ... + + server { + listen 443 ssl; + server_name demo.example.com; -1. In a browser, enter the address of your NGINX Plus instance. You should be directed to the okta login page, as shown in the example below. - {{< img src="img/sso/okta/Okta-login-window.png" >}} -1. Try to log in using the credentials of a user who is part of your organization. + ssl_certificate /etc/ssl/certs/fullchain.pem; + ssl_certificate_key /etc/ssl/private/key.pem; + + location / { + # ... + + proxy_pass http://127.0.0.1:8080; + } + } + # ... + } + ``` + +8. Protect this [location](https://nginx.org/en/docs/http/ngx_http_core_module.html#location) with Okta OIDC by specifying the [`auth_oidc`](https://nginx.org/en/docs/http/ngx_http_oidc_module.html#auth_oidc) directive that will point to the `okta` configuration specified in the [`oidc_provider {}`](https://nginx.org/en/docs/http/ngx_http_oidc_module.html#oidc_provider) context in [Step 5](#okta-setup-oidc-provider): + + ```nginx + # ... + location / { + auth_oidc okta; + + # ... + + proxy_pass http://127.0.0.1:8080; + } + # ... + ``` + +9. Pass the OIDC claims as headers to the application ([Step 10](#oidc_app)) with the [`proxy_set_header`](https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_set_header) directive. These claims are extracted from the ID token returned by Okta: + + - [`$oidc_claim_sub`](https://nginx.org/en/docs/http/ngx_http_oidc_module.html#var_oidc_claim_) - a unique `Subject` identifier assigned for each user by Okta + + - [`$oidc_claim_email`](https://nginx.org/en/docs/http/ngx_http_oidc_module.html#var_oidc_claim_) the e-mail address of the user + + - [`$oidc_claim_name`](https://nginx.org/en/docs/http/ngx_http_oidc_module.html#var_oidc_claim_) - the full name of the user + + - any other OIDC claim using the [`$oidc_claim_ `](https://nginx.org/en/docs/http/ngx_http_oidc_module.html#var_oidc_claim_) variable + + ```nginx + # ... + location / { + auth_oidc okta; + + proxy_set_header sub $oidc_claim_sub; + proxy_set_header email $oidc_claim_email; + proxy_set_header name $oidc_claim_name; + + proxy_pass http://127.0.0.1:8080; + } + # ... + ``` + + +10. Create a simple test application referenced by the `proxy_pass` directive which returns the authenticated user's full name and email upon successful authentication: + + ```nginx + # ... + server { + listen 8080; + + location / { + return 200 "Hello, $http_name!\nEmail: $http_email\nSub: $http_sub\n"; + default_type text/plain; + } + } + ``` +11. Save the NGINX configuration file and reload the configuration: + ```nginx + nginx -s reload + ``` + +### Complete Example + +This configuration example summarizes the steps outlined above. It includes only essential settings such as specifying the DNS resolver, defining the OIDC provider, configuring SSL, and proxying requests to an internal server. + +```nginx +http { + # Use a public DNS resolver for Issuer discovery, etc. + resolver 10.0.0.1 ipv4=on valid=300s; + + # Define the OIDC provider block for Okta + oidc_provider okta { + # The 'issuer' is your Okta issuer URL + # For okta dev it looks like: https://dev-123456.okta.com/oauth2/default + issuer https://dev-123456.okta.com/oauth2/default; + + # Your Okta “Client ID” from the application settings + client_id ; + + # Your Okta “Client Secret” from the application settings + client_secret ; + } + + server { + listen 443 ssl; + server_name demo.example.com; + + ssl_certificate /etc/ssl/certs/fullchain.pem; + ssl_certificate_key /etc/ssl/private/key.pem; + + location / { + # Enforce OIDC authentication with Okta + auth_oidc okta; + + # Pass OIDC claims as HTTP headers to the backend + proxy_set_header sub $oidc_claim_sub; + proxy_set_header email $oidc_claim_email; + proxy_set_header name $oidc_claim_name; + + proxy_pass http://127.0.0.1:8080; + } + } + + server { + # Simple backend listening on 8080 + listen 8080; + + location / { + return 200 "Hello, $http_name!\nEmail: $http_email\nSub: $http_sub\n"; + default_type text/plain; + } + } +} +``` + +### Testing + +1. Open `https://demo.example.com/` in a browser. You will be automatically redirected to the Okta sign-in page. + +2. Enter valid Okta credentials of a user who has access the application. Upon successful sign-in, Okta redirects you back to NGINX Plus, and you will see the proxied application content (for example, “Hello, Jane Doe!”). {{}}If you restricted access to a group of users, be sure to select a user who has access to the application.{{}} -## Troubleshooting -Refer to the [Troubleshooting](https://github.com/nginxinc/nginx-openid-connect#troubleshooting) section in the `nginx-openid-connect` repository on GitHub. +## Legacy njs-based Okta Solution {#legacy-njs-guide} + +If you are running NGINX Plus R33 and earlier or if you still need the njs-based solution, refer to the [Legacy njs-based Okta Guide]({{< ref "nginx/deployment-guides/single-sign-on/oidc-njs/okta.md" >}}) for details. The solution uses the [`nginx-openid-connect`](https://github.com/nginxinc/nginx-openid-connect) GitHub repository and NGINX JavaScript files. + + +## See Also + +- [NGINX Plus Native OIDC Module Reference documentation](https://nginx.org/en/docs/http/ngx_http_oidc_module.html) + +- [Release Notes for NGINX Plus R34]({{< ref "nginx/releases.md#r34" >}}) + + +## Revision History -### Revision History +- Version 1 (March 2025) – Initial version (NGINX Plus Release 34) -- Version 3 (March 2022) – Full edit incorporating updates to _Configuring Okta_ and _Configuring NGINX Plus_ -- Version 2 (March 2020) – Updates to _Configuring NGINX Plus_ section -- Version 1 (April 2019) – Initial version (NGINX Plus Release 17) diff --git a/content/nginx/deployment-guides/single-sign-on/onelogin.md b/content/nginx/deployment-guides/single-sign-on/onelogin.md index e4c5c4fa1..c3c2091ac 100644 --- a/content/nginx/deployment-guides/single-sign-on/onelogin.md +++ b/content/nginx/deployment-guides/single-sign-on/onelogin.md @@ -1,158 +1,293 @@ --- -description: Learn how to enable single sign-on (SSO) with [OneLogin](https://www.onelogin.com/) - for applications proxied by F5 NGINX Plus. -docs: DOCS-467 +description: Enable OpenID Connect-based single sign-on (SSO) for applications proxied by NGINX Plus, using OneLogin as the identity provider (IdP). +doctypes: +- task title: Single Sign-On with OneLogin toc: true -weight: 100 -type: -- tutorial +weight: 600 --- -
+This guide explains how to enable single sign-on (SSO) for applications being proxied by F5 NGINX Plus. The solution uses OpenID Connect as the authentication mechanism, with [OneLogin](https://www.onelogin.com/) as the Identity Provider (IdP) and NGINX Plus as the Relying Party (RP), or OIDC client application that verifies user identity. -This documentation applies to F5 NGINX Plus R15 and later. -
+{{< note >}} This guide applies to [NGINX Plus Release 34]({{< ref "nginx/releases.md#r34" >}}) and later. In earlier versions, NGINX Plus relied on an [njs-based solution](#legacy-njs-guide), which required NGINX JavaScript files, key-value stores, and advanced OpenID Connect logic. In the latest NGINX Plus version, the new [OpenID Connect module](https://nginx.org/en/docs/http/ngx_http_oidc_module.html) simplifies this process to just a few directives.{{< /note >}} -You can use NGINX Plus with [OneLogin](https://www.onelogin.com/) and the OpenID Connect protocol to enable single sign-on (SSO) for your proxied applications. By following the steps in this guide, you will learn how to set up SSO using OpenID Connect as the authentication mechanism, with OneLogin as the identity provider (IdP) and NGINX Plus as the relying party. - -{{< see-also >}}{{< include "nginx-plus/nginx-openid-repo-note.txt" >}}{{< /see-also >}} ## Prerequisites -To complete the steps in this guide, you need the following: +- An [OneLogin](https://www.onelogin.com/) account with administrator privileges. -- A OneLogin tenant with administrator privileges. -- [NGINX Plus](https://www.f5.com/products/nginx/nginx-plus) with a valid subscription. -- The [NGINX JavaScript module](https://www.nginx.com/products/nginx/modules/nginx-javascript/) (`njs`) -- the `njs` module handles the interaction between NGINX Plus and OneLogin identity provider (IdP). +- An NGINX Plus [subscription](https://www.f5.com/products/nginx/nginx-plus) and NGINX Plus [Release 34](({{< ref "nginx/releases.md#r34" >}})) or later. For installation instructions, see [Installing NGINX Plus](https://docs.nginx.com/nginx/admin-guide/installing-nginx/installing-nginx-plus/). -## Install NGINX Plus and the njs Module {#install-nginx-plus-njs} +- A domain name pointing to your NGINX Plus instance, for example, `demo.example.com`. -1. If you do not already have NGINX Plus installed, follow the [NGINX Plus Admin Guide](https://docs.nginx.com/nginx/admin-guide/installing-nginx/installing-nginx-plus/) steps to do so. -2. Install the NGINX JavaScript module by following the steps in the [`njs` installation guide](https://nginx.org/en/docs/njs/install.html). -3. Add the following directive to the top-level ("main") configuration context in the NGINX Plus configuration (`/etc/nginx/nginx.conf`) to load the `njs` module: - ```Nginx configuration file - load_module modules/ngx_http_js_module.so; - ``` +## Configure OneLogin {#onelogin-setup} -## Configure OneLogin {#config-onelogin} +### Create a OneLogin OIDC Application -**Note:** The following procedure reflects the OneLogin GUI at the time of publication, but the GUI is subject to change. Use this guide as a reference and adapt to the current OneLogin GUI as necessary. +1. Log in to your OneLogin admin console, for example, `https://.onelogin.com`. -Create a new application for NGINX Plus in the OneLogin GUI: +2. In the navigation bar, select **Applications**. -1. Log in to your OneLogin account at **https://**_domain_**.onelogin.com**, where _domain_ is the domain you chose when you created your account. +3. Click the **Add App** button. -2. Click  Applications  in the title bar and then click the  Add App  button in the upper right corner of the window that opens. + - On the **Find Applications** page, search for **OpenID Connect (OIDC)** and then select it. - + - Enter a **Display Name**, for example, `NGINX Demo App`. -3. On the **Find Applications** page that opens, type OpenID Connect in the search box. Click on the **OpenID Connect (OIDC)** row that appears. + - Select **Save**. - +4. In the app navigation, select **Configuration**. -4. On the **Add OpenId Connect (OIDC)** page that opens, change the value in the **Display Name** field to NGINX Plus and click the  Save  button. + - In **Redirect URIs**, add the callback URI for your NGINX Plus instance, for example, `https://demo.example.com/oidc_callback`. - + - Select **Save**. -5. When the save completes, a new set of choices appears in the left navigation bar. Click **Configuration**. In the **Redirect URI's** field, type the URI of the NGINX Plus instance including the port number, and ending in **/\_codexch** (in this guide it is https://my-nginx.example.com:443/_codexch). Then click the  Save  button. +5. In the app navigation, select **SSO**. - **Notes:** + - Copy the **Client ID**. You will need it later when configuring NGINX Plus. - - For production, we strongly recommend that you use SSL/TLS (port 443). - - The port number is mandatory even when you're using the default port for HTTP (80) or HTTPS (443). + - Select **Show client secret** and copy the **Client Secret**. You will need it later when configuring NGINX Plus. - + - Copy the **Issuer** URL, or OpenID Connect Discovery URL. You will need it later when configuring NGINX Plus. For OneLogin, the Issuer ID generally structured as: - -6. When the save completes, click **SSO** in the left navigation bar. Click Show client secret below the **Client Secret** field. Record the values in the **Client ID** and **Client Secret** fields. You will add them to the NGINX Plus configuration in [Step 4 of _Configuring NGINX Plus_](#nginx-plus-variables). + `https://.onelogin.com/oidc/2` - + See [Provider Configuration](https://developers.onelogin.com/openid-connect/api/provider-config) for details. - -7. Assign users to the application (in this guide, NGINX Plus) to enable them to access it for SSO. OneLogin recommends using [roles](https://onelogin.service-now.com/kb_view_customer.do?sysparm_article=KB0010606) for this purpose. You can access the **Roles** page under  Users  in the title bar. +{{< note >}} You will need the values of **Client ID**, **Client Secret**, and **Issuer** in the next steps. {{< /note >}} - +### Assign Users and Groups - -## Set up NGINX Plus +1. In the app navigation, select **Users** > **Roles**. -Take the steps in this section to set up NGINX Plus as the OpenID Connect Client. +2. Add users and groups who should have access to this application. -### Configure NGINX OpenID Connect {#nginx-plus-oidc-config} -1. Clone the [nginx-openid-connect](https://github.com/nginxinc/nginx-openid-connect) GitHub repository, or download the repository files. +## Set up NGINX Plus {#nginx-plus-setup} - ```shell - git clone https://github.com/nginxinc/nginx-openid-connect.git - ``` +With Onelogin configured, you can enable OIDC on NGINX Plus. NGINX Plus serves as the Rely Party (RP) application — a client service that verifies user identity. -1. Run the _configure.sh_ script to update the NGINX configuration files with the values for your OneLogin application. +1. Ensure that you are using the latest version of NGINX Plus by running the `nginx -v` command in a terminal: - For example: + ```shell + nginx -v + ``` + The output should match NGINX Plus Release 34 or later: - ```bash - ./nginx-openid-connect/configure.sh \ - --auth_jwt_key request \ - --client_id 168d5600-9224-... \ - --client_secret c9210a67d09e85... \ - https://.onelogin.com/oidc/2/.well-known/openid-configuration + ```none + nginx version: nginx/1.27.4 (nginx-plus-r34) ``` -2. In the `frontend.conf` file, update the **my_backend** upstream with the address of the application that you want to add OIDC authorization to. +2. Ensure that you have the values of the **Client ID**, **Client Secret**, and **Issuer** obtained during [Onelogin Configuration](#onelogin-setup). + +3. In your preferred text editor, open the NGINX configuration file (`/etc/nginx/nginx.conf` for Linux or `/usr/local/etc/nginx/nginx.conf` for FreeBSD). - For example: +4. In the [`http {}`](https://nginx.org/en/docs/http/ngx_http_core_module.html#http) context, make sure your public DNS resolver is specified with the [`resolver`](https://nginx.org/en/docs/http/ngx_http_core_module.html#resolver) directive: By default, NGINX Plus re‑resolves DNS records at the frequency specified by time‑to‑live (TTL) in the record, but you can override the TTL value with the `valid` parameter: - ```Nginx configuration file - upstream my_backend { - zone my_backend 64k; - server my-backend-app.com:80; + ```nginx + http { + resolver 10.0.0.1 ipv4=on valid=300s; + + # ... } ``` -3. In the _openid_connect.server_conf_ file, add the [`proxy_set_header`](https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_set_header) directive to the `/_jwks_uri` and `/_token` locations to `Accept-Encoding "gzip"`, as shown below. + +5. In the [`http {}`](https://nginx.org/en/docs/http/ngx_http_core_module.html#http) context, define the OneLogin provider named `onelogin` by specifying the [`oidc_provider {}`](https://nginx.org/en/docs/http/ngx_http_oidc_module.html#oidc_provider) context: + + ```nginx + http { + resolver 10.0.0.1 ipv4=on valid=300s; - ```Nginx configuration file - ... - location = /_jwks_uri { - ... - proxy_set_header Accept-Encoding "gzip" + oidc_provider onelogin { + + # ... + + } + # ... } - ... - location = /_token { - ... - proxy_set_header Accept-Encoding "gzip" + ``` + +6. In the [`oidc_provider {}`](https://nginx.org/en/docs/http/ngx_http_oidc_module.html#oidc_provider) context, specify: + + - your actual OneLogin **Client ID** obtained in [OneLogin Configuration](#onelogin-create) with the [`client_id`](https://nginx.org/en/docs/http/ngx_http_oidc_module.html#client_id) directive + + - your **Client Secret** obtained in [OneLogin Configuration](#onelogin-create) with the [`client_secret`](https://nginx.org/en/docs/http/ngx_http_oidc_module.html#client_secret) directive + + - the **Issuer** URL obtained in [OneLogin Configuration](#onelogin-create) with the [`issuer`](https://nginx.org/en/docs/http/ngx_http_oidc_module.html#client_secret) directive + + The `issuer` is typically your OneLogin OIDC URL: + + `https://.onelogin.com/oidc/2`. + + - **Important:** All interaction with the IdP is secured exclusively over SSL/TLS, so NGINX must trust the certificate presented by the IdP. By default, this trust is validated against your system’s CA bundle (the default CA store for your Linux or FreeBSD distribution). If the IdP’s certificate is not included in the system CA bundle, you can explicitly specify a trusted certificate or chain with the [`ssl_trusted_certificate`](https://nginx.org/en/docs/http/ngx_http_oidc_module.html#ssl_trusted_certificate) directive so that NGINX can validate and trust the IdP’s certificate. + + ```nginx + http { + resolver 10.0.0.1 ipv4=on valid=300s; + + oidc_provider onelogin { + issuer https://.onelogin.com/oidc/2; + client_id ; + client_secret ; + } + + # ... + } + ``` + +7. Make sure you have configured a [server](https://nginx.org/en/docs/http/ngx_http_core_module.html#server) that corresponds to `demo.example.com`, and there is a [location](https://nginx.org/en/docs/http/ngx_http_core_module.html#location) that [points](https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_pass) to your application (see [Step 10](#oidc_app)) at `http://127.0.0.1:8080` that is going to be OIDC-protected: + + ```nginx + http { + # ... + + server { + listen 443 ssl; + server_name demo.example.com; + + ssl_certificate /etc/ssl/certs/fullchain.pem; + ssl_certificate_key /etc/ssl/private/key.pem; + + location / { + # ... + + proxy_pass http://127.0.0.1:8080; + } + } + # ... } - ... ``` -4. Copy the following files to the _/etc/nginx/conf.d_ directory on the host machine where NGINX Plus is installed: +8. Protect this [location](https://nginx.org/en/docs/http/ngx_http_core_module.html#location) with OneLogin OIDC by specifying the [`auth_oidc`](https://nginx.org/en/docs/http/ngx_http_oidc_module.html#auth_oidc) directive that will point to the `onelogin` configuration specified in the [`oidc_provider {}`](https://nginx.org/en/docs/http/ngx_http_oidc_module.html#oidc_provider) context in [Step 5](#onelogin-setup-oidc-provider): - - `frontend.conf` - - `openid_connect.js` - - `openid_connect.server_conf` - - `openid_connect_configuration.conf` + ```nginx + # ... + location / { + auth_oidc onelogin; -5. Reload the NGINX configuration: + # ... - ```bash - sudo nginx -s reload + proxy_pass http://127.0.0.1:8080; + } + # ... ``` -## Test Your Setup +9. Pass the OIDC claims as headers to the application ([Step 10](#oidc_app)) with the [`proxy_set_header`](https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_set_header) directive. These claims are extracted from the ID token returned by OneLogin: + + - [`$oidc_claim_sub`](https://nginx.org/en/docs/http/ngx_http_oidc_module.html#var_oidc_claim_) - a unique `Subject` identifier assigned for each user by OneLogin + + - [`$oidc_claim_email`](https://nginx.org/en/docs/http/ngx_http_oidc_module.html#var_oidc_claim_) the e-mail address of the user + + - [`$oidc_claim_name`](https://nginx.org/en/docs/http/ngx_http_oidc_module.html#var_oidc_claim_) - the full name of the user + + - any other OIDC claim using the [`$oidc_claim_ `](https://nginx.org/en/docs/http/ngx_http_oidc_module.html#var_oidc_claim_) variable + + + ```nginx + # ... + location / { + auth_oidc onelogin; + + proxy_set_header sub $oidc_claim_sub; + proxy_set_header email $oidc_claim_email; + proxy_set_header name $oidc_claim_name; + + proxy_pass http://127.0.0.1:8080; + } + # ... + ``` + + +10. Create a simple test application referenced by the `proxy_pass` directive which returns the authenticated user's full name and email upon successful authentication: + + ```nginx + # ... + server { + listen 8080; + + location / { + return 200 "Hello, $http_name!\nEmail: $http_email\nSub: $http_sub\n"; + default_type text/plain; + } + } + ``` +11. Save the NGINX configuration file and reload the configuration: + ```nginx + nginx -s reload + ``` + +### Complete Example + +This configuration example summarizes the steps outlined above. It includes only essential settings such as specifying the DNS resolver, defining the OIDC provider, configuring SSL, and proxying requests to an internal server. + +```nginx +http { + # Use a public DNS resolver for Issuer discovery, etc. + resolver 10.0.0.1 ipv4=on valid=300s; + + oidc_provider onelogin { + # The 'issuer' is typically your OneLogin OIDC base URL + # e.g. https://.onelogin.com/oidc/2 + issuer https://.onelogin.com/oidc/2; + + # Replace with your actual OneLogin Client ID and Secret + client_id ; + client_secret ; + } + + server { + listen 443 ssl; + server_name demo.example.com; + + ssl_certificate /etc/ssl/certs/fullchain.pem; + ssl_certificate_key /etc/ssl/private/key.pem; + + location / { + # Protect this path with OneLogin OIDC + auth_oidc onelogin; + + # Forward OIDC claims to the upstream as headers if desired + proxy_set_header sub $oidc_claim_sub; + proxy_set_header email $oidc_claim_email; + proxy_set_header name $oidc_claim_name; + proxy_pass http://127.0.0.1:8080; + } + } + + server { + listen 8080; + + location / { + return 200 "Hello, $http_name!\nYour email is $http_email\nSub: $http_sub\n"; + default_type text/plain; + } + } +} +``` + +### Testing + +1. Open `https://demo.example.com/` in a browser. You will be automatically redirected to the OneLogin sign-in page. + +2. Enter valid OneLogin credentials of a user who has access the application. Upon successful sign-in, OneLogin redirects you back to NGINX Plus, and you will see the proxied application content (for example, “Hello, Jane Doe!”). + +{{}}If you restricted access to a group of users, be sure to select a user who has access to the application.{{}} + + +## Legacy njs-based OneLogin Solution {#legacy-njs-guide} + +If you are running NGINX Plus R33 and earlier or if you still need the njs-based solution, refer to the [Legacy njs-based OneLogin Guide]({{< ref "nginx/deployment-guides/single-sign-on/oidc-njs/onelogin.md" >}}) for details. The solution uses the [`nginx-openid-connect`](https://github.com/nginxinc/nginx-openid-connect) GitHub repository and NGINX JavaScript files. + -In a browser, enter the address of your NGINX Plus instance and try to log in using the credentials of a user assigned to the application (see [Step 7 of _Configuring OneLogin_](#onelogin-roles)). +## See Also - +- [NGINX Plus Native OIDC Module Reference documentation](https://nginx.org/en/docs/http/ngx_http_oidc_module.html) - -## Troubleshooting +- [Release Notes for NGINX Plus R34]({{< ref "nginx/releases.md#r34" >}}) -Refer to the [Troubleshooting](https://github.com/nginxinc/nginx-openid-connect#troubleshooting) section in the `nginx-openid-connect` repository on GitHub. -### Revision History +## Revision History -- Version 3 (May 2022) - Updates OneLogin's OpenId Connect API endpoints from version 1 to version 2 -- Version 2 (March 2020) – Updates to _Configuring NGINX Plus_ section -- Version 1 (July 2019) – Initial version (NGINX Plus Release 18) +- Version 1 (March 2025) – Initial version (NGINX Plus Release 34) diff --git a/content/nginx/deployment-guides/single-sign-on/ping-identity.md b/content/nginx/deployment-guides/single-sign-on/ping-identity.md index dfe13ef38..804cc55e3 100644 --- a/content/nginx/deployment-guides/single-sign-on/ping-identity.md +++ b/content/nginx/deployment-guides/single-sign-on/ping-identity.md @@ -1,193 +1,299 @@ --- -description: Enable OpenID Connect-based single-sign for applications proxied by NGINX - Plus, using Ping Identity as the identity provider (IdP). -docs: DOCS-468 +description: Enable OpenID Connect-based single sign-on (SSO) for applications proxied by NGINX Plus, using Ping Identity as the identity provider (IdP). +doctypes: +- task title: Single Sign-On with Ping Identity toc: true -weight: 100 -type: -- how-to +weight: 800 --- -This guide explains how to enable single sign-on (SSO) for applications being proxied by F5 NGINX Plus. The solution uses OpenID Connect as the authentication mechanism, with Ping Identity as the identity provider (IdP) and NGINX Plus as the relying party. +This guide explains how to enable single sign-on (SSO) for applications being proxied by F5 NGINX Plus. The solution uses OpenID Connect as the authentication mechanism, with [Ping Identity](https://www.pingidentity.com/en.html) (PingFederate or PingOne) as the Identity Provider (IdP), and NGINX Plus as the Relying Party. -The instructions in this document apply to both Ping Identity's on‑premises and cloud products, PingFederate and PingOne for Enterprise. +{{< note >}} This guide applies to [NGINX Plus Release 34]({{< ref "nginx/releases.md#r34" >}}) and later. In earlier versions, NGINX Plus relied on an [njs-based solution](#legacy-njs-guide), which required NGINX JavaScript files, key-value stores, and advanced OpenID Connect logic. In the latest NGINX Plus version, the new [OpenID Connect module](https://nginx.org/en/docs/http/ngx_http_oidc_module.html) simplifies this process to just a few directives.{{< /note >}} -{{< see-also >}}{{< include "nginx-plus/nginx-openid-repo-note.txt" >}}{{< /see-also >}} - ## Prerequisites -The instructions assume you have the following: +- [PingFederate](https://docs.pingidentity.com/pingfederate/latest/pf_pf_landing_page.html) Enterprise Federation Server or [PingOne](https://docs.pingidentity.com/pingone/p1_cloud__platform_main_landing_page.html) Cloud deployment with a Ping Identity account. -- A running deployment of PingFederate or PingOne for Enterprise, and a Ping Identity account. For installation and configuration instructions, see the documentation for [PingFederate](https://docs.pingidentity.com/bundle/pingfederate-93/page/tau1564002955783.html) or [PingOne for Enterprise](https://docs.pingidentity.com/bundle/pingone/page/fjn1564020491958-1.html). -- An NGINX Plus subscription and NGINX Plus R15 or later. For installation instructions, see the [NGINX Plus Admin Guide](https://docs.nginx.com/nginx/admin-guide/installing-nginx/installing-nginx-plus/). -- The NGINX JavaScript module (njs), required for handling the interaction between NGINX Plus and the IdP. After installing NGINX Plus, install the module with the command for your operating system. +- An NGINX Plus [subscription](https://www.f5.com/products/nginx/nginx-plus) and NGINX Plus [Release 34](({{< ref "nginx/releases.md#r34" >}})) or later. For installation instructions, see [Installing NGINX Plus](https://docs.nginx.com/nginx/admin-guide/installing-nginx/installing-nginx-plus/). - For Debian and Ubuntu: +- A domain name pointing to your NGINX Plus instance, for example, `demo.example.com`. - ```none - sudo apt install nginx-plus-module-njs - ``` - For CentOS, RHEL, and Oracle Linux: +## Configure PingFederate or PingOne for Enterprise {#ping-create} - ```shell - sudo yum install nginx-plus-module-njs - ``` +{{< note >}} These steps outline an example with the cloud offering of **PingOne**. If you are using the on‑premises PingFederate, the user interface might slightly differ. {{< /note >}} -- The following directive included in the top-level ("main") configuration context in **/etc/nginx/nginx.conf**, to load the NGINX JavaScript module: +Create a new application for NGINX Plus: - ```nginx - load_module modules/ngx_http_js_module.so; - ``` +1. Log in to your Ping Identity admin console. + +2. Go to **Applications** > **Applications**. - -## Configuring PingFederate or PingOne for Enterprise +3. Click the **+** (plus) symbol to create a new OIDC Application. -**Note:** This guide uses the GUI provided with PingOne for Enterprise. It reflects the GUI at the time of initial publication, but the GUI is subject to change. The PingFederate user interace might also differ. Use this guide as a reference and adapt as necessary for the UI you are using. +4. On the New Application screen: -Create a new application for NGINX Plus: + - Enter the Name of your application, for example, `nginx-demo-app`. -1. Log in to your Ping Identity account. The administrative dashboard opens automatically. In this guide, we show the PingOne for Enterprise dashboard, and for brevity refer simply to ”PingOne”. + - Select the Application Type **OIDC Web App**. -2. Click  APPLICATIONS  in the title bar, and on the **My Applications** page that opens, click **OIDC** and then the + Add Application button. + - Select **Save**. - +5. In your OIDC application, Select the **Overview** tab: -3. The Add OIDC Application window pops up. Click the ADVANCED CONFIGURATION box, and then the  Next  button. + - in the **General** section, copy your **Client ID** and **Client Secret** values. You will need then later when configuring NGINX Plus. - + - In the **Connection Details** section, copy your **Issuer ID**. You will need it later when configuring NGINX Plus. -4. In section 1 (PROVIDE DETAILS ABOUT YOUR APPLICATION), type a name in the **APPLICATION NAME** field and a short description in the **SHORT DESCRIPTION** field. Here, we're using nginx-plus-application and NGINX Plus. Choose a value from the **CATEGORY** drop‑down menu; here we’re using Information Technology. You can also add an icon if you wish. Click the  Next  button. + For PingOne Cloud, the Issuer ID generally structured as `https://auth.pingone.com//as`. - + For PingFederate, the Issuer ID generally structured as `https://pingfederate.example.com:9031` appended with the realm path of your environment. -5. In section 2 (AUTHORIZATION SETTINGS), perform these steps: +6. On the **Configuration** tab of your OIDC application: - 1. Under **GRANTS**, click both Authorization Code and Implicit.
- 2. Under **CREDENTIALS**, click the + Add Secret button. PingOne creates a client secret and opens the **CLIENT SECRETS** field to display it, as shown in the screenshot. To see the actual value of the secret, click the eye icon.
- 3. Click the  Next  button. + - In the **Redirect URIs** field, add the NGINX Plus callback URI, for example: - + `https://demo.example.com/oidc_callback`. -6. In section 3 (SSO FLOW AND AUTHENTICATION SETTINGS): + - Select **Save**. - 1. In the START SSO URL field, type the URL where users access your application. Here we’re using https://example.com. - 2. In the **REDIRECT URIS** field, type the URI of the NGINX Plus instance including the port number, and ending in **/\_codexch**. Here we’re using https://my-nginx-plus.example.com:443/\_codexch (the full value is not visible in the screenshot). +7. Assign the application to the appropriate **Groups** or **Users** who will be allowed to log in. - **Notes:** +{{< note >}} You will need the values of **Client ID**, **Client Secret**, and **Issuer** in the next steps. {{< /note >}} - - For production, we strongly recommend that you use SSL/TLS (port 443). - - The port number is mandatory even when you're using the default port for HTTP (80) or HTTPS (443). - +## Set up NGINX Plus {#nginx-plus-setup} -7. In section 4 (DEFAULT USER PROFILE ATTRIBUTE CONTRACT), optionally add attributes to the required sub and idpid attributes, by clicking the + Add Attribute button. We’re not adding any in this example. When finished, click the  Next  button. +With PingOne or PingFederate configured, you can enable OIDC on NGINX Plus. NGINX Plus serves as the Rely Party (RP) application — a client service that verifies user identity. - +1. Ensure that you are using the latest version of NGINX Plus by running the `nginx -v` command in a terminal: -8. In section 5 (CONNECT SCOPES), click the circled plus-sign on the OpenID Profile (profile) and OpenID Profile Email (email) scopes in the LIST OF SCOPES column. They are moved to the **CONNECTED SCOPES** column, as shown in the screenshot. Click the  Next  button. + ```shell + nginx -v + ``` + The output should match NGINX Plus Release 34 or later: - + ```none + nginx version: nginx/1.27.4 (nginx-plus-r34) + ``` -9. In section 6 (ATTRIBUTE MAPPING), map attributes from your identity repository to the claims available to the application. The one attribute you must map is **sub**, and here we have selected the value Email from the drop‑down menu (the screenshot is abridged for brevity). +2. Ensure that you have the values of the **Client ID**, **Client Secret**, and **Issuer** obtained during [PingOne or PingFederate Configuration](#ping-setup). - +3. In your preferred text editor, open the NGINX configuration file (`/etc/nginx/nginx.conf` for Linux or `/usr/local/etc/nginx/nginx.conf` for FreeBSD). - -10. In section 7 (GROUP ACCESS), select the groups that will have access to the application, by clicking the circled plus-sign on the corresponding boxes in the **AVAILABLE GROUPS** column. The boxes move to the **ADDED GROUPS** column. As shown in the screenshot we have selected the two default groups, Domain Administrators@directory and Users@directory. +4. In the [`http {}`](https://nginx.org/en/docs/http/ngx_http_core_module.html#http) context, make sure your public DNS resolver is specified with the [`resolver`](https://nginx.org/en/docs/http/ngx_http_core_module.html#resolver) directive: By default, NGINX Plus re‑resolves DNS records at the frequency specified by time‑to‑live (TTL) in the record, but you can override the TTL value with the `valid` parameter: - Click the  Done  button. + ```nginx + http { + resolver 10.0.0.1 ipv4=on valid=300s; - + # ... + } + ``` -11. You are returned to the **My Applications** window, which now includes a row for nginx-plus-application. Click the toggle switch at the right end of the row to the “on” position, as shown in the screenshot. Then click the “expand” icon at the end of the row, to display the application’s details. + +5. In the [`http {}`](https://nginx.org/en/docs/http/ngx_http_core_module.html#http) context, define the PingOne or PingFederate provider named `ping` by specifying the [`oidc_provider {}`](https://nginx.org/en/docs/http/ngx_http_oidc_module.html#oidc_provider) context: - + ```nginx + http { + resolver 10.0.0.1 ipv4=on valid=300s; + + oidc_provider ping { + + # ... + } + # ... + } + ``` - -12. On the page that opens, make note of the values in the following fields on the **Details** tab. You will add them to the NGINX Plus configuration in [Step 4 of _Configuring NGINX Plus_](#nginx-plus-variables). +6. In the [`oidc_provider {}`](https://nginx.org/en/docs/http/ngx_http_oidc_module.html#oidc_provider) context, specify: - - **CLIENT ID** (in the screenshot, 28823604-83c5-4608-88da-c73fff9c607a) - - **CLIENT SECRETS** (in the screenshot, 7GMKILBofxb...); click on the eye icon to view the actual value + - your actual Ping **Client ID** obtained in [Ping Configuration](#ping-create) with the [`client_id`](https://nginx.org/en/docs/http/ngx_http_oidc_module.html#client_id) directive - + - your **Client Secret** obtained in [Ping Configuration](#ping-create) with the [`client_secret`](https://nginx.org/en/docs/http/ngx_http_oidc_module.html#client_secret) directive - -## Configuring NGINX Plus + - the **Issuer** URL obtained in [Ping Configuration](#ping-create) with the [`issuer`](https://nginx.org/en/docs/http/ngx_http_oidc_module.html#client_secret) directive -Configure NGINX Plus as the OpenID Connect relying party: + The `issuer` is typically your Ping Identity OIDC URL. -1. Create a clone of the [nginx-openid-connect](https://github.com/nginxinc/nginx-openid-connect) GitHub repository. + For PingOne Cloud, the URL is `https://auth.pingone.com//as`. - ```shell - git clone https://github.com/nginxinc/nginx-openid-connect - ``` + For PingFederate, the URL is `https://pingfederate.example.com:9031` followed by your environment’s realm path. -2. Copy these files from the clone to **/etc/nginx/conf.d**: + By default, NGINX Plus creates the OpenID metadata URL by appending the `/.well-known/openid-configuration` part to the Issuer URL. If your metadata URL is different, you can explicitly specify the metadata document with the [`config_url`](https://nginx.org/en/docs/http/ngx_http_oidc_module.html#config_url) directive. - - **frontend.conf** - - **openid\_connect.js** - - **openid\_connect.server\_conf** + - **Important:** All interaction with the IdP is secured exclusively over SSL/TLS, so NGINX must trust the certificate presented by the IdP. By default, this trust is validated against your system’s CA bundle (the default CA store for your Linux or FreeBSD distribution). If the IdP’s certificate is not included in the system CA bundle, you can explicitly specify a trusted certificate or chain with the [`ssl_trusted_certificate`](https://nginx.org/en/docs/http/ngx_http_oidc_module.html#ssl_trusted_certificate) directive so that NGINX can validate and trust the IdP’s certificate. - -3. Get the URLs for the authorization endpoint, token endpoint, and JSON Web Key (JWK) file from the Ping Identity configuration. Run the following `curl` command in a terminal, piping the output to the indicated `python` command to output the entire configuration in an easily readable format. We've abridged the output to show only the relevant fields. - The `` variable is the value in the **CLIENT ID** field that you noted in [Step 12 of _Configuring PingFederate or PingOne for Enterprise_](#ping-client-id-secrets). + ```nginx + http { + resolver 10.0.0.1 ipv4=on valid=300s; - **Note:** This `curl` command is appropriate for Ping One for Enterprise. For PingFederate, you might need to replace `sso.connect.pingidentity.com` with the IP address of your local PingFederate server. + oidc_provider ping { + issuer https://auth.pingone.com//as; + client_id ; + client_secret ; + } - ```shell - $ curl sso.connect.pingidentity.com//.well-known/openid-configuration | python -m json.tool - ... - { - "authorization_endpoint": "https://sso.connect.pingidentity.com/sso/as/authorization.oauth2", - ... - "jwks_uri": "https://sso.connect.pingidentity.com/sso/as/jwks", - ... - "token_endpoint": "https://sso.connect.pingidentity.com/sso/as/token.oauth2", - ... + # ... } ``` - -4. In your preferred text editor, open **/etc/nginx/conf.d/frontend.conf**. Change the second parameter of each of the following [set](http://nginx.org/en/docs/http/ngx_http_rewrite_module.html#set) directives to the specified value: +7. Make sure you have configured a [server](https://nginx.org/en/docs/http/ngx_http_core_module.html#server) that corresponds to `demo.example.com`, and there is a [location](https://nginx.org/en/docs/http/ngx_http_core_module.html#location) that [points](https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_pass) to your application (see [Step 10](#oidc_app)) at `http://127.0.0.1:8080` that is going to be OIDC-protected: + + ```nginx + http { + + # ... + + server { + listen 443 ssl; + server_name demo.example.com; + + ssl_certificate /etc/ssl/certs/fullchain.pem; + ssl_certificate_key /etc/ssl/private/key.pem; + + location / { + # ... + + proxy_pass http://127.0.0.1:8080; + } + } + # ... + } + ``` + +8. Protect this [location](https://nginx.org/en/docs/http/ngx_http_core_module.html#location) with Ping Identity OIDC by specifying the [`auth_oidc`](https://nginx.org/en/docs/http/ngx_http_oidc_module.html#auth_oidc) directive that will point to the `ping` configuration specified in the [`oidc_provider {}`](https://nginx.org/en/docs/http/ngx_http_oidc_module.html#oidc_provider) context in [Step 5](#ping-setup-oidc-provider): + + ```nginx + # ... + location / { + auth_oidc ping; + + # ... + + proxy_pass http://127.0.0.1:8080; + } + # ... + ``` + +9. Pass the OIDC claims as headers to the application ([Step 10](#oidc_app)) with the [`proxy_set_header`](https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_set_header) directive. These claims are extracted from the ID token returned by Ping: + + - [`$oidc_claim_sub`](https://nginx.org/en/docs/http/ngx_http_oidc_module.html#var_oidc_claim_) - a unique `Subject` identifier assigned for each user by Ping Identity + + - [`$oidc_claim_email`](https://nginx.org/en/docs/http/ngx_http_oidc_module.html#var_oidc_claim_) - the e-mail address of the user + + - [`$oidc_claim_name`](https://nginx.org/en/docs/http/ngx_http_oidc_module.html#var_oidc_claim_) - the full name of the user + + - any other OIDC claim using the [`$oidc_claim_ `](https://nginx.org/en/docs/http/ngx_http_oidc_module.html#var_oidc_claim_) variable - - `set $oidc_authz_endpoint` – Value of `authorization_endpoint` from [Step 3](#nginx-plus-urls) (in this guide, `https://sso.connect.pingidentity.com/sso/as/authorization.oauth2`) - - `set $oidc_token_endpoint` – Value of `token_endpoint` from [Step 3](#nginx-plus-urls) (in this guide, `https://sso.connect.pingidentity.com/sso/as/token.oauth2`) - - `set $oidc_client` – Value in the **CLIENT ID** field in [Step 12 of _Configuring PingFederate or PingOne for Enterprise_](#ping-client-id-secrets) (in this guide, `28823604-83c5-4608-88da-c73fff9c607a`) - - `set $oidc_client_secret` – Value in the **CLIENT SECRETS** field in [Step 12 of _Configuring PingFederate or PingOne for Enterprise_](#ping-client-id-secrets) (in this guide, `7GMKILBofxb...`) - - `set $oidc_hmac_key` – A unique, long, and secure phrase + {{< note >}} Ensure the `openid`, `profile`, `email` Scopes are enabled in Ping Identity.{{< /note >}} + + ```nginx + # ... + location / { + auth_oidc ping; + + proxy_set_header sub $oidc_claim_sub; + proxy_set_header email $oidc_claim_email; + proxy_set_header name $oidc_claim_name; + + proxy_pass http://127.0.0.1:8080; + } + # ... + ``` + + +10. Create a simple test application referenced by the `proxy_pass` directive which returns the authenticated user's full name and email upon successful authentication: + + ```nginx + # ... + server { + listen 8080; + + location / { + return 200 "Hello, $http_name!\nEmail: $http_email\nSub: $http_sub\n"; + default_type text/plain; + } + } + ``` +11. Save the NGINX configuration file and reload the configuration: + ```nginx + nginx -s reload + ``` + +### Complete Example + +This configuration example summarizes the steps outlined above. It includes only essential settings such as specifying the DNS resolver, defining the OIDC provider, configuring SSL, and proxying requests to an internal server. + +```nginx +http { + # Use a public DNS resolver for Issuer discovery, etc. + resolver 10.0.0.1 ipv4=on valid=300s; + + oidc_provider ping { + # The issuer is typically something like: + # https://auth.pingone.com//as + issuer https://auth.pingone.com//as; + + # Your Ping Identity Client ID and Secret + client_id ; + client_secret ; + } + + server { + listen 443 ssl; + server_name demo.example.com; + + ssl_certificate /etc/ssl/certs/fullchain.pem; + ssl_certificate_key /etc/ssl/private/key.pem; + + location / { + # Enforce OIDC with Ping Identity + auth_oidc ping; + + # Forward OIDC claims as headers if desired + proxy_set_header sub $oidc_claim_sub; + proxy_set_header email $oidc_claim_email; + proxy_set_header name $oidc_claim_name; + + proxy_pass http://127.0.0.1:8080; + } + } + + server { + # Simple backend application for demonstration + listen 8080; + + location / { + return 200 "Hello, $http_name!\nEmail: $http_email\nSub: $http_sub\n"; + default_type text/plain; + } + } +} +``` -5. Configure the JWK file. The procedure depends on which version of NGINX Plus you are using. +### Testing - - In NGINX Plus R17 and later, NGINX Plus can read the JWK file directly from the URL reported as `jwks_uri` in [Step 3](#nginx-plus-urls). Change **/etc/nginx/conf.d/frontend.conf** as follows: +1. Open `https://demo.example.com/` in a browser. You will be automatically redirected to the PingOne sign-in page. - 1. Comment out (or remove) the [auth_jwt_key_file](http://nginx.org/en/docs/http/ngx_http_auth_jwt_module.html#auth_jwt_key_file) directive. - 2. Uncomment the [auth_jwt_key_request](http://nginx.org/en/docs/http/ngx_http_auth_jwt_module.html#auth_jwt_key_request) directive. (Its parameter, `/_jwks_uri`, refers to the value of the `$oidc_jwt_keyfile` variable, which you set in the next step.) - 3. Change the second parameter of the `set $oidc_jwt_keyfile` directive to the value reported in the `jwks_uri` field in [Step 3](#nginx-plus-urls) (in this guide, `https://sso.connect.pingidentity.com/sso/as/jwks`). +2. Enter valid Ping Identity credentials of a user who has access the application. Upon successful sign-in, PingOne redirects you back to NGINX Plus, and you will see the proxied application content (for example, “Hello, Jane Doe!”). - - In NGINX Plus R16 and earlier, the JWK file must be on the local disk. (You can also use this method with NGINX Plus R17 and later if you wish.) - 1. Copy the JSON contents from the JWK file named in the `jwks_uri` field in [Step 3](#nginx-plus-urls) (in this guide, `https://sso.connect.pingidentity.com/sso/as/jwks`) to a local file (for example, `/etc/nginx/my_ping_identity_jwk.json`). - 2. In **/etc/nginx/conf.d/frontend.conf**, change the second parameter of the `set $oidc_jwt_keyfile` directive to the local file path. +## Legacy njs-based Ping Identity Solution {#legacy-njs-guide} -6. Confirm that the user named by the [user](http://nginx.org/en/docs/ngx_core_module.html#user) directive in the NGINX Plus configuration (in **/etc/nginx/nginx.conf** by convention) has read permission on the JWK file. +If you are running NGINX Plus R33 and earlier or if you still need the njs-based solution, refer to the [Legacy njs-based Ping Identity Guide]({{< ref "nginx/deployment-guides/single-sign-on/oidc-njs/ping-identity.md" >}}) for details. The solution uses the [`nginx-openid-connect`](https://github.com/nginxinc/nginx-openid-connect) GitHub repository and NGINX JavaScript files. - -## Testing -In a browser, enter the address of your NGINX Plus instance and try to log in using the credentials of a user assigned to the application (see [Step 10 of _PingFederate or PingOne for Enterprise_](#ping-group-access)). +## See Also - +- [NGINX Plus Native OIDC Module Reference documentation](https://nginx.org/en/docs/http/ngx_http_oidc_module.html) - -## Troubleshooting +- [Release Notes for NGINX Plus R34]({{< ref "nginx/releases.md#r34" >}}) -See the [**Troubleshooting**](https://github.com/nginxinc/nginx-openid-connect#troubleshooting) section at the nginx-openid-connect repository on GitHub. -### Revision History +## Revision History -- Version 2 (March 2020) – Updates to _Configuring NGINX Plus_ section -- Version 1 (January 2020) – Initial version (NGINX Plus Release 20) +- Version 1 (March 2025) – Initial version for NGINX Plus Release 34 From 6fa858dbd2a369dd90ed05fe1dd80b7e5a15b750 Mon Sep 17 00:00:00 2001 From: Yaroslav Zhuravlev Date: Wed, 12 Mar 2025 09:55:59 +0000 Subject: [PATCH 02/12] Updated OSS Components for R34. --- content/nginx/open-source-components.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/content/nginx/open-source-components.md b/content/nginx/open-source-components.md index 838f1eaa8..6ecd01ee2 100644 --- a/content/nginx/open-source-components.md +++ b/content/nginx/open-source-components.md @@ -12,13 +12,13 @@ type: Open source components included in the F5 NGINX Plus (package name is `nginx-plus`) are: -- nginx/OSS 1.27.2, distributed under 2-clause BSD license. +- nginx/OSS 1.27.4, distributed under 2-clause BSD license. Homepage: Copyright © 2002-2021 Igor Sysoev - Copyright © 2011-2024 NGINX, Inc. + Copyright © 2011-2025 NGINX, Inc. All rights reserved. @@ -51,7 +51,7 @@ modification, are permitted. - MurmurHash algorithm (version 2), distributed under MIT license. - Homepage: + Homepage: Copyright © Austin Appleby @@ -226,7 +226,7 @@ like `import/require()` and will resolve them (6.8.1). Copyright © 2013-present, Facebook, Inc. - - `regenerator-runtime`, standalone runtime for Regenerator-compiled generator and async functions (0.13.9). + - `regenerator-runtime`, standalone runtime for Regenerator-compiled generator and async functions (0.14.1). Homepage: From b4653360b64846dfd99cc125accc9c4dc12d024a Mon Sep 17 00:00:00 2001 From: Yaroslav Zhuravlev Date: Mon, 17 Mar 2025 17:52:10 +0000 Subject: [PATCH 03/12] Modified deprecation notice for OpenTracing dynamic module. --- content/nginx/admin-guide/dynamic-modules/opentracing.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/content/nginx/admin-guide/dynamic-modules/opentracing.md b/content/nginx/admin-guide/dynamic-modules/opentracing.md index dd89c65e1..4b609050f 100644 --- a/content/nginx/admin-guide/dynamic-modules/opentracing.md +++ b/content/nginx/admin-guide/dynamic-modules/opentracing.md @@ -12,13 +12,13 @@ type: - how-to --- -{{< note >}} The module is deprecated since F5 NGINX Plus Release 32. The [OpenTelemetry]({{< ref "opentelemetry.md" >}}) module is available since NGINX Plus Release 29 that incorporates the features of the OpenTracing module.{{< /note >}} +{{< note >}} The module no longer available since F5 NGINX Plus [Release 34]({{< ref "nginx/releases.md#r34" >}}). The [OpenTelemetry]({{< ref "nginx/admin-guide/dynamic-modules/opentelemetry.md" >}}) module is available since NGINX Plus [Release 29]({{< ref "nginx/releases.md#r29" >}}) that incorporates the features of the OpenTracing module.{{< /note >}} ## Installation -1. Check the [Technical Specifications]({{< relref "../../technical-specs.md" >}}) page to verify that the module is supported by your operating system. +1. Check the [Technical Specifications]({{< ref "nginx/technical-specs.md" >}}) page to verify that the module is supported by your operating system. 2. Install the OpenTracing module package `nginx-plus-module-opentracing`. @@ -89,6 +89,6 @@ After installation you will need to enable and configure the module in NGINX Plu - [NGINX plugin for OpenTracing Reference](https://github.com/opentracing-contrib/nginx-opentracing) -- [NGINX Dynamic Modules]({{< ref "dynamic-modules.md" >}}) +- [NGINX Dynamic Modules]({{< ref "nginx/admin-guide/dynamic-modules/dynamic-modules.md" >}}) -- [NGINX Plus Technical Specifications]({{< relref "../../technical-specs.md" >}}) +- [NGINX Plus Technical Specifications]({{< ref "nginx/technical-specs.md" >}}) From 3fab8829b7259a21c3820e9aa6776ba32d0125fc Mon Sep 17 00:00:00 2001 From: Yaroslav Zhuravlev Date: Thu, 13 Mar 2025 11:36:23 +0000 Subject: [PATCH 04/12] Updated NGINX Plus installation for R34. --- .../installing-nginx/installing-nginx-plus.md | 50 ++----------------- 1 file changed, 5 insertions(+), 45 deletions(-) diff --git a/content/nginx/admin-guide/installing-nginx/installing-nginx-plus.md b/content/nginx/admin-guide/installing-nginx/installing-nginx-plus.md index 8b5d30c99..13c64f54c 100644 --- a/content/nginx/admin-guide/installing-nginx/installing-nginx-plus.md +++ b/content/nginx/admin-guide/installing-nginx/installing-nginx-plus.md @@ -78,46 +78,6 @@ This article explains how to install NGINX Plus on different operating systems, 1. {{< include "nginx-plus/install/install-nginx-agent-for-nim.md" >}} -## Install NGINX Plus on RHEL 7.4+, CentOS 7.4+, and Oracle Linux 7.4+ {#install_rhel_centos} - -{{< call-out "important" "Deprecation notice" "" >}} -CentOS 7.4, RHEL 7.4, and Oracle Linux 7.4 are deprecated as of NGINX Plus Release 32 (R32) and are not supported in Release 33 (R33) or later. For the list of supported distributions, refer to the [NGINX Plus Tech Specs]({{< ref "nginx/technical-specs.md" >}}). -{{}} - -1. {{< include "nginx-plus/install/back-up-config-and-logs.md" >}} - -1. Download the SSL certificate and private key associated with your NGINX Plus subscription from the MyF5 Customer Portal: - - - Log in to [MyF5](https://my.f5.com/manage/s/). - - Go to **My Products & Plans > Subscriptions** to see your active subscriptions. - - Find your NGINX products or services subscription, and select the **Subscription ID** for details. - - Download the **nginx-repo.crt** and **nginx-repo.key** from the subscription page. - -1. {{< include "nginx-plus/install/install-ca-certificates-dependency-yum.md" >}} - -1. {{< include "nginx-plus/install/create-dir-for-crt-key.md" >}} - -1. {{< include "nginx-plus/install/copy-crt-and-key.md" >}} - -1. Add the NGINX Plus repository by downloading the [nginx-plus-7.4.repo](https://cs.nginx.com/static/files/nginx-plus-7.4.repo) file to **/etc/yum.repos.d**: - - ```shell - sudo wget -P /etc/yum.repos.d https://cs.nginx.com/static/files/nginx-plus-7.4.repo - ``` - -
- Learn how to pin NGINX Plus to a specific version - {{}}{{< include "nginx-plus/install/pin-to-version/pin-rhel7-R32.md" >}}{{}} -
- -1. {{< include "nginx-plus/install/install-nginx-plus-package-yum.md" >}} - -1. {{< include "nginx-plus/install/enable-nginx-service-at-boot.md" >}} - -1. {{< include "nginx-plus/install/check-nginx-binary-version.md" >}} - -1. {{< include "nginx-plus/install/install-nginx-agent-for-nim.md" >}} - ## Install NGINX Plus on RHEL 8.1+, Oracle Linux 8.1+, AlmaLinux 8, Rocky Linux 8 {#install_rhel8} 1. {{< include "nginx-plus/install/check-tech-specs.md" >}} @@ -546,10 +506,10 @@ For a community dynamic module to work with NGINX Plus, it must be compiled alo - Identify the NGINX Open Source version that corresponds to your version of NGINX Plus. See [NGINX Plus Releases]({{< ref "nginx/releases.md" >}}). - - Download the sources for the appropriate NGINX Open Source mainline version, in this case 1.27.2: + - Download the sources for the appropriate NGINX Open Source mainline version, in this case 1.27.4: ```shell - wget -qO - https://nginx.org/download/nginx-1.27.2.tar.gz | tar zxfv - + wget -qO - https://nginx.org/download/nginx-1.27.4.tar.gz | tar zxfv - ``` 1. Obtain the source for the dynamic module. @@ -565,7 +525,7 @@ For a community dynamic module to work with NGINX Plus, it must be compiled alo First establish binary compatibility by running the `configure` script with the `‑‑with‑compat` option. Then compile the module with `make modules`. ```shell - cd nginx-1.27.2/ + cd nginx-1.27.4/ ./configure --with-compat --add-dynamic-module=../ make modules ``` @@ -580,7 +540,7 @@ For a community dynamic module to work with NGINX Plus, it must be compiled alo 1. Make a copy of the module file and include the NGINX Open Source version in the filename. This makes it simpler to manage multiple versions of a dynamic module in the production environment. ```shell - cp objs/ngx_http_hello_world.so ./ngx_http_hello_world_1.27.2.so + cp objs/ngx_http_hello_world.so ./ngx_http_hello_world_1.27.4.so ``` ### Enabling Dynamic Modules {#enable_dynamic} @@ -886,7 +846,7 @@ To upgrade your NGINX Plus installation to the newest version: The output of the command: ```shell - nginx version: nginx/1.27.2 (nginx-plus-r33) + nginx version: nginx/1.27.4 (nginx-plus-r34) ``` ## Upgrade NGINX Plus Modules {#upgrade_modules} From 0b659d7e8b390a44464194a1cd7c60d9fb9ffa63 Mon Sep 17 00:00:00 2001 From: Yaroslav Zhuravlev Date: Mon, 17 Mar 2025 19:29:53 +0000 Subject: [PATCH 05/12] Tech specs for r34. --- content/nginx/technical-specs.md | 42 ++++++++++++++++---------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/content/nginx/technical-specs.md b/content/nginx/technical-specs.md index 7ee616da0..76f3f339a 100644 --- a/content/nginx/technical-specs.md +++ b/content/nginx/technical-specs.md @@ -15,20 +15,19 @@ NGINX Plus is available only as a binary; it is not distributed as source code. ## Supported Distributions {#supported-distributions} {{}} -| Distribution | Supported on R33 | Supported on R32 | -|-------------------------------------|-----------------------------------------------|-----------------------------------------------| -| AlmaLinux | 8 (x86_64, aarch64)
9 (x86_64, aarch64) | 8 (x86_64, aarch64)
9 (x86_64, aarch64) | -| Alpine Linux | 3.17 (x86_64, aarch64) **(deprecated)**
3.18 (x86_64, aarch64)
3.19 (x86_64, aarch64)
3.20 (x86_64, aarch64) **(new)** | 3.16 (x86_64, aarch64) **(deprecated)**
3.17 (x86_64, aarch64)
3.18 (x86_64, aarch64)
3.19 (x86_64, aarch64) | -| Amazon Linux | 2023 (x86_64, aarch64) | 2023 (x86_64, aarch64) | -| Amazon Linux 2 | LTS (x86_64, aarch64) | LTS (x86_64, aarch64) | -| CentOS | **Not supported** | 7.4+ (x86_64) **(deprecated)** | -| Debian | 11 (x86_64, aarch64)
12 (x86_64, aarch64) | 11 (x86_64, aarch64)
12 (x86_64, aarch64) | -| FreeBSD | 13 (amd64)
14 (amd64) | 13 (amd64)
14 (amd64) | -| Oracle Linux | 8.1+ (x86_64, aarch64)
9 (x86_64) | 7.4+ (x86_64) **(deprecated)**
8.1+ (x86_64, aarch64)
9 (x86_64) | -| Red Hat Enterprise Linux (RHEL) | 8.1+ (x86_64, aarch64)
9.0+ (x86_64, aarch64) | 7.4+ (x86_64) **(deprecated)**
8.1+ (x86_64, aarch64)
9.0+ (x86_64, aarch64) | -| Rocky Linux | 8 (x86_64, aarch64)
9 (x86_64, aarch64) | 8 (x86_64, aarch64)
9 (x86_64, aarch64) | -| SUSE Linux Enterprise Server (SLES) | 12 SP5 (x86_64) **(deprecated)**
15 SP2+ (x86_64) | 12 SP5 (x86_64)
15 SP2+ (x86_64) | -| Ubuntu | 20.04 LTS (x86_64, aarch64)
22.04 LTS (x86_64, aarch64)
24.04 LTS (x86_64, aarch64) | 20.04 LTS (x86_64, aarch64)
22.04 LTS (x86_64, aarch64)
24.04 LTS (x86_64, aarch64 **(new)** | +| Distribution | Supported on R34 | Supported on R33 | +|-------------------------------------|----------------------------------------------------|--------------------------------------------------------| +| AlmaLinux | 8 (x86_64, aarch64)
9 (x86_64, aarch64) | 8 (x86_64, aarch64)
9 (x86_64, aarch64) | +| Alpine Linux | 3.18 (x86_64, aarch64) **(deprecated)**
3.19 (x86_64, aarch64)
3.20 (x86_64, aarch64)
3.21 (x86_64, aarch64) **(new)** | 3.17 (x86_64, aarch64) **(deprecated)**
3.18 (x86_64, aarch64)
3.19 (x86_64, aarch64)
3.20 (x86_64, aarch64) **(new)** | +| Amazon Linux | 2023 (x86_64, aarch64) | 2023 (x86_64, aarch64) | +| Amazon Linux 2 | LTS (x86_64, aarch64) **(deprecated)** | LTS (x86_64, aarch64) | +| Debian | 11 (x86_64, aarch64)
12 (x86_64, aarch64) | 11 (x86_64, aarch64)
12 (x86_64, aarch64) | +| FreeBSD | 13 (amd64)
14 (amd64) | 13 (amd64)
14 (amd64) | +| Oracle Linux | 8.1+ (x86_64, aarch64)
9 (x86_64) | 8.1+ (x86_64, aarch64)
9 (x86_64) | +| Red Hat Enterprise Linux (RHEL) | 8.1+ (x86_64, aarch64)
9.0+ (x86_64, aarch64) | 8.1+ (x86_64, aarch64)
9.0+ (x86_64, aarch64) | +| Rocky Linux | 8 (x86_64, aarch64)
9 (x86_64, aarch64) | 8 (x86_64, aarch64)
9 (x86_64, aarch64) | +| SUSE Linux Enterprise Server (SLES) | 15 SP2+ (x86_64) | 12 SP5 (x86_64) **(deprecated)**
15 SP2+ (x86_64) | +| Ubuntu | 20.04 LTS (x86_64, aarch64) **(deprecated)**
22.04 LTS (x86_64, aarch64)
24.04 LTS (x86_64, aarch64) | 20.04 LTS (x86_64, aarch64)
22.04 LTS (x86_64, aarch64)
24.04 LTS (x86_64, aarch64) | {{
}} --- @@ -41,13 +40,11 @@ Dynamic modules are supported on the [same distributions as NGINX Plus](#support | Module | Distribution and details | |-------------------|-----------------------------------------------------------------------------------------------------------| | AppProtect | AlmaLinux/Rocky Linux: **Not supported**
Alpine Linux: **Not supported**
Amazon Linux 2: **x86_64 only**
Amazon Linux 2023: **Not supported**
Debian 11: **x86_64 only**
FreeBSD: **Not supported**
Oracle Linux 8: **x86_64 only**
RHEL 8: **x86_64 only**
SLES: **Not supported**
Ubuntu 20.04: **x86_64 only** | -| Brotli | SLES 12: **Not supported** | -| GeoIP | RHEL/Oracle Linux/AlmaLinux/Rocky Linux 8.0+, 9: **Not supported**
FreeBSD: **Not supported** | -| GeoIP2 | SLES 12: **Not supported**
Amazon Linux 2: **Not supported** | +| GeoIP | Amazon Linux 2023 **Not supported**
RHEL/Oracle Linux/AlmaLinux/Rocky Linux 8.0+, 9: **Not supported**
FreeBSD: **Not supported** | +| GeoIP2 | Amazon Linux 2: **Not supported** | | HA-Keepalived | FreeBSD: **Not supported**
Alpine Linux: **Not supported**
Amazon Linux 2: **Not supported**
Amazon Linux 2023: **Not supported** | | NGINX sync | FreeBSD: **Not supported**
Alpine Linux: **Not supported** | -| OpenTelemetry | Amazon Linux 2: **Not supported**
SLES: **Not supported** | -| OpenTracing | SLES 12: **Not supported** | +| OpenTelemetry | Amazon Linux 2: **Not supported**
SLES: **Not supported** | | {{}} --- @@ -66,7 +63,9 @@ You can configure which protocols to enable or disable with the [ssl_protocols]( TLSv1.2 and earlier are supported on all operating systems listed in [Supported Distributions](#supported-distributions). -TLSv1.3 is supported starting in NGINX Plus R17 and is enabled by default in NGINX Plus R29 and later. It requires OpenSSL 1.1.1 or higher. Note that not all operating systems supported by NGINX Plus include OpenSSL 1.1.1. Check your operating system's documentation to confirm TLSv1.3 compatibility. +TLSv1.3 is supported starting from NGINX Plus R17 and is enabled by default in NGINX Plus R29 and later. It requires OpenSSL 1.1.1 or higher. Note that not all operating systems supported by NGINX Plus include OpenSSL 1.1.1. Check your operating system's documentation to confirm TLSv1.3 compatibility. + +TLSv1.2 and TLSv1.3 are the default SSL protocols starting from NGINX Plus R34 (if supported by the OpenSSL library). If OpenSSL 1.0.0 or older is used, the default SSL protocols are TLSv1 and TLSv1.1. --- @@ -119,6 +118,7 @@ See [Sizing Guide for Deploying NGINX Plus on Bare Metal Servers](https://www.ng - [Auth Basic](https://nginx.org/en/docs/http/ngx_http_auth_basic_module.html) – Implement HTTP Basic Authentication scheme - [Auth JWT](https://nginx.org/en/docs/http/ngx_http_auth_jwt_module.html) – Validate JSON Web Tokens - [Auth Request](https://nginx.org/en/docs/http/ngx_http_auth_request_module.html) – Determine client authorization using subrequests to external authentication server +- [OIDC](https://nginx.org/en/docs/http/ngx_http_oidc_module.html) – Implement authentication as a Relying Party in OpenID Connect solution - [Referer](https://nginx.org/en/docs/http/ngx_http_referer_module.html) – Control access based on `Referer` field in HTTP request header - [Secure Link](https://nginx.org/en/docs/http/ngx_http_secure_link_module.html) – Process encrypted, time-limited links to content @@ -182,7 +182,7 @@ See [Sizing Guide for Deploying NGINX Plus on Bare Metal Servers](https://www.ng - [NGINX Plus API](https://nginx.org/en/docs/http/ngx_http_api_module.html) – Provide REST API for accessing metrics, configuring upstream server groups dynamically, and managing key-value pairs, without the need to reload NGINX configuration - [Key-Value Store](https://nginx.org/en/docs/http/ngx_http_keyval_module.html) – Create variables with values taken from key-value pairs managed by the [NGINX Plus API](https://nginx.org/en/docs/http/ngx_http_api_module.html#http_keyvals_) -- [Management](https://nginx.org/en/docs/ngx_mgmt_module.html) – Configure licensing and usage reporting of NGINX Plus installation to F5 licensing endpoint or [NGINX Instance Manager]({{< ref "nim/index.md" >}}) +- [Management](https://nginx.org/en/docs/ngx_mgmt_module.html) – [Configure licensing and usage reporting]({{< ref "solutions/about-subscription-licenses.md" >}}) of NGINX Plus installation to F5 licensing endpoint or [NGINX Instance Manager]({{< ref "nim/index.md" >}}) ### TCP and UDP Proxying and Load Balancing From 32feda2433752b89cc997dd080c404e3f9c2909d Mon Sep 17 00:00:00 2001 From: Yaroslav Zhuravlev Date: Mon, 17 Mar 2025 15:53:16 +0000 Subject: [PATCH 06/12] Release Notes for R34. --- content/nginx/releases.md | 48 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/content/nginx/releases.md b/content/nginx/releases.md index 25b8d2802..9fb127822 100644 --- a/content/nginx/releases.md +++ b/content/nginx/releases.md @@ -18,6 +18,54 @@ We do not issue updates for releases that have reached EoSD. For this reason, we The initial release dates for NGINX Plus are noted in this document. New releases are announced on the [NGINX Product Support Announcements](https://interact.f5.com/Customer-Preference-Center.html) mailing list. + +## NGINX Plus Release 34 (R34) +_01 April 2025_
+_Based on NGINX Open Source 1.27.4_ + +NGINX Plus R34 is a feature release: + +- OIDC authentication support via native [ngx_http_oidc_module](https://nginx.org/en/docs/http/ngx_http_oidc_module.html) module. + +- NGINX usage reporting: [proxy](https://nginx.org/en/docs/ngx_mgmt_module.html#proxy) support. + +- [Caching](https://blog.nginx.org/blog/optimizing-resource-usage-for-complex-ssl-configurations) of SSL certificates and secret keys with variables for [http](https://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_certificate_cache) and [stream](https://nginx.org/en/docs/stream/ngx_stream_ssl_module.html#ssl_certificate_cache). + +- The [`keepalive_min_timeout`](https://nginx.org/en/docs/http/ngx_http_core_module.html#keepalive_min_timeout) directive. + +- TLSv1.2 and TLSv1.3 are the default [SSL protocols](https://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_protocols) (if supported by the OpenSSL library). If OpenSSL 1.0.0 or older is used, the default SSL protocols are TLSv1 and TLSv1.1. + +- Bugfixes in QUIC and HTTP/3, mail proxy, the MP4 module, the `proxy_store` and `proxy_bind` directives. + +- Security: insufficient check in virtual servers handling with TLSv1.3 SNI allowed to reuse SSL sessions in a different virtual server, to bypass client SSL certificates verification ([CVE-2025-23419](http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2025-23419)). + + +NGINX Plus R34 is supported on: + +- AlmaLinux 8, 9 +- Alpine Linux 3.18, 3.19, 3.20, 3.21 +- Amazon Linux 2 LTS, 2023 +- Debian 11, 12 +- FreeBSD 13, 14 +- Oracle Linux 8.1+, 9 +- RHEL 8.1+, 9.0+ +- Rocky Linux 8, 9 +- SUSE Linux Enterprise Server 15 SP5+ +- Ubuntu 20.04 LTS, 22.04 LTS, 24.04 LTS + +**Notes:** + +- Alpine Linux 3.17 is removed +- Alpine Linux 3.18 is deprecated +- Alpine Linux 3.21 is new in this release +- Amazon Linux 2 LTS is deprecated +- SUSE Linux Enterprise Server 12 is removed +- Ubuntu 20.04 is deprecated +- the [OpenTracing]({{< relref "nginx/admin-guide/dynamic-modules/opentracing.md" >}}) dynamic module is no longer available. It is recommended to use the [OpenTelemetry Distributed Tracing]({{< relref "nginx/admin-guide/dynamic-modules/opentelemetry.md" >}}) module, which incorporates all the features of the OpenTracing module. + +More information: [Announcing NGINX Plus R34](https://community.f5.com/kb/technicalarticles/f5-nginx-plus-r34-release-now-available/340300) + + ## NGINX Plus Release 33 (R33) _19 November 2024_
From 26bd534dbbaf3eddbe6afed17d90c2c1c44bae02 Mon Sep 17 00:00:00 2001 From: Yaroslav Zhuravlev Date: Tue, 18 Mar 2025 13:34:10 +0000 Subject: [PATCH 07/12] Added info about proxy to subscription licenses. --- .../solutions/about-subscription-licenses.md | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/content/solutions/about-subscription-licenses.md b/content/solutions/about-subscription-licenses.md index 1a964d7c7..2157ead8e 100644 --- a/content/solutions/about-subscription-licenses.md +++ b/content/solutions/about-subscription-licenses.md @@ -65,11 +65,22 @@ To ensure NGINX Plus R33 or later can send usage reports, follow these steps bas ### For internet-connected environments -Allow outbound HTTPS traffic on TCP port `443` to communicate with F5's licensing endpoint (`product.connect.nginx.com`). Ensure that the following IP addresses are allowed: +1. Allow outbound HTTPS traffic on TCP port `443` to communicate with F5's licensing endpoint (`product.connect.nginx.com`). Ensure that the following IP addresses are allowed: -- `3.135.72.139` -- `3.133.232.50` -- `52.14.85.249` + - `3.135.72.139` + - `3.133.232.50` + - `52.14.85.249` + +2. (Optional, R34 and later) If your company enforces a strict outbound traffic policy, you can use an outbound proxy for establishing an end-to-end tunnel to the F5 licensing endpoint. On each NGINX Plus instance, update the [`proxy`](https://nginx.org/en/docs/ngx_mgmt_module.html#proxy) directive in the [`mgmt`](https://nginx.org/en/docs/ngx_mgmt_module.html) block of the NGINX configuration (`/etc/nginx/nginx.conf`) to point to the company's outbound proxy server: + + + ```nginx + mgmt { + proxy PROXY_ADDR:PORT; #can be http or https + proxy_username USER; #optional + proxy_password PASS; #optional + } + ``` ### For network-restricted environments From bd0a3bd14633540f5ac8c2f5b17a37425e00ab60 Mon Sep 17 00:00:00 2001 From: Yaroslav Zhuravlev Date: Tue, 1 Apr 2025 10:04:04 +0100 Subject: [PATCH 08/12] Added OIDC article. --- .../security-controls/configuring-oidc.md | 353 ++++++++++++++++++ content/nginx/releases.md | 4 +- static/nginx/images/oidc.png | Bin 0 -> 108296 bytes 3 files changed, 355 insertions(+), 2 deletions(-) create mode 100644 content/nginx/admin-guide/security-controls/configuring-oidc.md create mode 100644 static/nginx/images/oidc.png diff --git a/content/nginx/admin-guide/security-controls/configuring-oidc.md b/content/nginx/admin-guide/security-controls/configuring-oidc.md new file mode 100644 index 000000000..f087db72e --- /dev/null +++ b/content/nginx/admin-guide/security-controls/configuring-oidc.md @@ -0,0 +1,353 @@ +--- +description: Enable OpenID Connect-based single sign-on (SSO) for applications proxied by NGINX Plus, using an Identity Provider (IdP). +doctypes: +- task +title: Single Sign-On with OpenID Connect and Identity Providers +toc: true +weight: 550 +--- + +This guide explains how to enable single sign-on (SSO) for applications being proxied by F5 NGINX Plus using: +- OpenID Connect as the authentication mechanism +- an external Identity Provider (IdP) such as AD FS, Auth0, Cognito, Entra ID, Keycloak, OneLogin, Okta, Ping Identity and others +- NGINX Plus as an OIDC client application that verifies user identity (Relying Party). + +OpenID Connect is an identity protocol that utilizes the authorization and authentication mechanisms of OAuth 2.0. With it, NGINX Plus can provide a layer of authentication for protected applications that do not natively support it. + +NGINX Plus consumes authorization and claims from OpenID Connect Identity Providers by utilizing JWT-based identity tokens that are delivered via the OAuth 2.0 framework. It supports several specific flows suitable for browser-based and desktop/mobile applications. OpenID Connect allows the client (NGINX Plus) to retrieve an ID token in addition to an access token. The ID token provides information about the authenticated user. + +For the target client application, OIDC authentication can be enabled with great flexibility on different levels. It can be enabled globally, or more granular at a per-server or a per-location level. Additionally, OIDC supports auto discovery and retrieval of the OpenID provider configuration metadata while also allowing the definition of additional metadata if needed. + + +## OpenID Connect Workflow and NGINX Plus {#oidc-workflow} + +1. A user accesses a protected resource. + +2. NGINX Plus redirects the user to the IdP for user authentication and authorization. + +3. The IdP collects user credentials and authenticates the user. + +4. The IdP redirects the user back to NGINX Plus with an authorization code. + +5. NGINX Plus retrieves an `id_token` and access the token using the authorization code from the IdP. + +6. NGINX Plus validates the `id_token` and retrieves profile data for the user using the `UserInfo` endpoint. The retrieved profile data is validated and the content of the `id_token` and the profile data is used for providing access control to the client application. + +7. Upon successful validation, the resource access request is sent to the client application along with the access token. + +8. The client application validates the access token and based on the token validation, the resource access request is allowed or denied. + + +{{< img src="nginx/images/oidc.png">}} + + + +## Tested Identity Providers {#deployment-guildes} + + NGINX Plus has tested support with the following Identity Providers: + +{{}} +| IdP Provider | Resource | +|-----------------|-------------------------------------------------------------------------------------------------------------| +| Amazon Cognito | [Deployment Guide for Amazon Cognito]({{< ref "nginx/deployment-guides/single-sign-on/cognito.md" >}}) | +| Auth0 | [Deployment Guide for Auth0]({{< ref "nginx/deployment-guides/single-sign-on/auth0.md" >}}) | +| Microsoft AD FS | [Deployment Guide for AD FS]({{< ref "nginx/deployment-guides/single-sign-on/active-directory-federation-services.md" >}}) | +| Microsoft Entra / Azure ID| [Deployment Guide for Entra ID]({{< ref "nginx/deployment-guides/single-sign-on/entra-id.md" >}}) | +| Keycloak | [Deployment Guide for Keycloak]({{< ref "nginx/deployment-guides/single-sign-on/keycloak.md" >}}) | +| OneLogin | [Deployment Guide for OneLogin]({{< ref "nginx/deployment-guides/single-sign-on/onelogin.md" >}}) | +| Okta | [Deployment Guide for Okta]({{< ref "nginx/deployment-guides/single-sign-on/okta.md" >}}) | +| Ping Identity | [Deployment Guide for Ping Identity]({{< ref "nginx/deployment-guides/single-sign-on/ping-identity.md" >}}) | +{{}} + + +## Prerequisites {#prerequisites} + +- an Identity Provider application instance, either on-premises or in the cloud, with administrator privileges. + +- An NGINX Plus [subscription](https://www.f5.com/products/nginx/nginx-plus) and NGINX Plus [Release 34](({{< ref "nginx/releases.md#r34" >}})) or later. For installation instructions, see [Installing NGINX Plus](https://docs.nginx.com/nginx/admin-guide/installing-nginx/installing-nginx-plus/). + +- A domain name pointing to your NGINX Plus instance, for example, `demo.example.com`. + + +## Configure your IdP {#idp-setup} + +The workflow for each IdP provider is similar, but some steps may vary: + +1. Log in to you IdP admin console. + +2. Create an OpenID Connect (OIDC) application. + + - Provide a name for the application + - Assign relevant users and groups that will require access + +3. Obtain the Client ID and Client Secret in your IdP application. You will need them later when [configuring NGINX Plus as the Relying Party](#setup-oidc-provider2). + +4. Obtain the Issuer. + + The `issuer` value can be obtained from your IdP application or from the OpenID Connect Discovery URL provided by every IdP. Usually, the discovery URL is: + + `https://your-idp-domain/.well-known/openid-configuration` + + where: + + - the `your-idp-domain` is your IdP's server address + + - the `.well-known/openid-configuration` is the default address for IdPs for the `.well-known` document + + The IdP configuration metadata is returned in the JSON format, for example: + + ```json + { + ... + "issuer": "https://your-idp-domain/idp", + "authorization_endpoint": "https://your-idp-domain/idp/oauth2/authorize/", + "token_endpoint": "https://your-idp-domain/idp/oauth2/token/", + "jwks_uri": "https://your-idp-domain/idp/discovery/keys", + ... + } + ``` + + Copy the **issuer** value. You will need it later when [configuring NGINX Plus as the Relying Party](#setup-oidc-provider2). + + +## Configure the Relying Party (NGINX Plus) {#rp-setup} + +With your IdP configured, you can enable OIDC on NGINX Plus. NGINX Plus serves as the Rely Party (RP) client service that verifies user identity. + +1. Ensure that you are using the latest version of NGINX Plus by running the `nginx -v` command in a terminal: + + ```shell + nginx -v + ``` + The output should match NGINX Plus Release 34 or later: + + ```none + nginx version: nginx/1.27.4 (nginx-plus-r34) + ``` + +2. Ensure that you have the values of the **Client ID**, **Client Secret**, and **Issuer** obtained from your IdP Provider. + +3. In your preferred text editor, open the NGINX configuration file (`/etc/nginx/nginx.conf` for Linux or `/usr/local/etc/nginx/nginx.conf` for FreeBSD). + +4. In the [`http {}`](https://nginx.org/en/docs/http/ngx_http_core_module.html#http) context, make sure your public DNS resolver is specified with the [`resolver`](https://nginx.org/en/docs/http/ngx_http_core_module.html#resolver) directive: By default, NGINX Plus re‑resolves DNS records at the frequency specified by time‑to‑live (TTL) in the record, but you can override the TTL value with the `valid` parameter: + + ```nginx + http { + resolver 10.0.0.1 ipv4=on valid=300s; + + # ... + } + ``` + + +5. In the [`http {}`](https://nginx.org/en/docs/http/ngx_http_core_module.html#http) context, define the IdP provider named `my_idp` by specifying the [`oidc_provider {}`](https://nginx.org/en/docs/http/ngx_http_oidc_module.html#oidc_provider) context: + + ```nginx + http { + resolver 10.0.0.1 ipv4=on valid=300s; + + oidc_provider my_idp { + + # ... + + } + # ... + } + ``` + + +6. In the [`oidc_provider {}`](https://nginx.org/en/docs/http/ngx_http_oidc_module.html#oidc_provider) context, specify: + + - your actual **Client ID** obtained from your IdP with the [`client_id`](https://nginx.org/en/docs/http/ngx_http_oidc_module.html#client_id) directive + + - your **Client Secret** obtained from your IdP with the [`client_secret`](https://nginx.org/en/docs/http/ngx_http_oidc_module.html#client_secret) directive + + - the **Issuer** URL obtained from your IdP with the [`issuer`](https://nginx.org/en/docs/http/ngx_http_oidc_module.html#client_secret) directive + + By default, NGINX Plus creates the metadata URL by appending the `/.well-known/openid-configuration` part to the Issuer URL. If your Issuer is different, you can explicitly specify the metadata document with the [`config_url`](https://nginx.org/en/docs/http/ngx_http_oidc_module.html#config_url) directive. + + - a valid system CA bundle with the [`ssl_trusted_certificate`](https://nginx.org/en/docs/http/ngx_http_oidc_module.html#ssl_trusted_certificate) so that NGINX Plus could validate the IdP TLS certificates: + + ```nginx + http { + resolver 10.0.0.1 ipv4=on valid=300s; + + oidc_provider my_idp { + issuer https://your-idp-domain/idp; + client_id ; + client_secret ; + + ssl_trusted_certificate /etc/ssl/certs/ca-certificates.crt; + } + + # ... + } + ``` + +7. Make sure you have configured a [server](https://nginx.org/en/docs/http/ngx_http_core_module.html#server) that corresponds to `demo.example.com`, and there is a [location](https://nginx.org/en/docs/http/ngx_http_core_module.html#location) that [points](https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_pass) to your application (see [Step 10](#oidc_app)) at `http://127.0.0.1:8080` that is going to be OIDC-protected: + + ```nginx + http { + # ... + + server { + listen 443 ssl; + server_name demo.example.com; + + ssl_certificate /etc/ssl/certs/fullchain.pem; + ssl_certificate_key /etc/ssl/private/key.pem; + + location / { + # ... + + proxy_pass http://127.0.0.1:8080; + } + } + # ... + } + ``` + +8. Protect this [location](https://nginx.org/en/docs/http/ngx_http_core_module.html#location) with IdP OIDC by specifying the [`auth_oidc`](https://nginx.org/en/docs/http/ngx_http_oidc_module.html#auth_oidc) directive that will point to the `my_idp` configuration specified in the [`oidc_provider {}`](https://nginx.org/en/docs/http/ngx_http_oidc_module.html#oidc_provider) context in [Step 5](#setup-oidc-provider): + + ```nginx + # ... + location / { + auth_oidc my_idp; + + # ... + + proxy_pass http://127.0.0.1:8080; + + } + # ... + ``` + +9. Pass the OIDC claims as headers to the application ([Step 10](#oidc_app)) with the [`proxy_set_header`](https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_set_header) directive. These claims are extracted from the ID token returned by the IdP: + + - [`$oidc_claim_sub`](https://nginx.org/en/docs/http/ngx_http_oidc_module.html#var_oidc_claim_) - a unique `Subject` identifier assigned for each user by the IdP + + - [`$oidc_claim_email`](https://nginx.org/en/docs/http/ngx_http_oidc_module.html#var_oidc_claim_) the e-mail address of the user + + - [`$oidc_claim_name`](https://nginx.org/en/docs/http/ngx_http_oidc_module.html#var_oidc_claim_) - the full name of the user + + - any other OIDC claim using the [`$oidc_claim_ `](https://nginx.org/en/docs/http/ngx_http_oidc_module.html#var_oidc_claim_) variable + + + ```nginx + # ... + location / { + auth_oidc my_idp; + + proxy_set_header sub $oidc_claim_sub; + proxy_set_header email $oidc_claim_email; + proxy_set_header name $oidc_claim_name; + + proxy_pass http://127.0.0.1:8080; + } + # ... + ``` + +10. Create a simple test application referenced by the `proxy_pass` directive which returns the authenticated user's full name and email upon successful authentication: + + ```nginx + # ... + server { + listen 8080; + + location / { + return 200 "Hello, $http_name!\nEmail: $http_email\nIdP sub sub: $http_sub\n"; + default_type text/plain; + } + } + ``` +11. Save the NGINX configuration file and reload the configuration: + ```nginx + nginx -s reload + ``` + +### Complete Example {#example} + +This configuration example summarizes the steps outlined above. It includes only essential settings such as specifying the DNS resolver, defining the OIDC provider, configuring SSL, and proxying requests to an internal server. + +```nginx +http { + # Use a public DNS resolver for Issuer discovery, etc. + resolver 10.0.0.1 ipv4=on valid=300s; + + oidc_provider my_idp { + # The 'issuer' typically matches your IdP's base URL + issuer https:///idp; + + # Provide a CA bundle for certificate validation + ssl_trusted_certificate /etc/ssl/certs/ca-certificates.crt; + + # Replace with your actual IdP's client_id and secret + client_id ; + client_secret ; + + # If the .well-known endpoint cannot be derived automatically, + # specify config_url: + # config_url https:///auth/realms/main/.well-known/openid-configuration; + } + + server { + listen 443 ssl; + server_name demo.example.com; +x + ssl_certificate /etc/ssl/certs/fullchain.pem; + ssl_certificate_key /etc/ssl/private/key.pem; + + location / { + # Protect this location with OIDC + auth_oidc my_idp; + + # Forward OIDC claims as headers if desired + proxy_set_header sub $oidc_claim_sub; + proxy_set_header email $oidc_claim_email; + proxy_set_header name $oidc_claim_name; + + proxy_pass http://127.0.0.1:8080; + } + } + + server { + # simple test oidc-protected application + listen 8080; + + location / { + return 200 "Hello, $http_name!\nEmail: $http_email\nIdP sub: $http_sub\n"; + default_type text/plain; + } + } +} +``` + +### Testing {#testing} + +1. Open https://demo.example.com/ in a browser. You should be redirected to your IdP's login page. + +2. Enter valid IdP credentials for a user assigned to the `nginx-demo-app` client. +Upon successful sign-in, the IdP redirects you back to NGINX Plus, and you will see the proxied application content (for example, “Hello, Jane Doe!”). + + +## Glossary {#glossary} + + +{{}} +| Term | Description | +|-------------------------|----------------------------------------------------| +| Identity Provider (IdP) | A service that authenticates users and verifies their identity for client applications. | +| Protected Resource | A resource that is hosted by the resource server and requires an access token to be accessed. | +| Relying Party (RP) | A client service required to verify user identity. | +| JSON Web Token (JWT) | An open standard (RFC 7519) that defines a compact and self-contained way for securely transmitting information between parties as a JSON object. This information can be verified and trusted because it is digitally signed. | +| ID Token | Specific to OIDC, the primary use of the token in JWT format is to provide information about the authentication operation's outcome. | +| Access Token | Defined in OAuth2, this (optional) short lifetime token provides access to specific user resources as defined in the scope values in the request to the authorization server (can be a JSON token as well). | +| Refresh Token | Coming from OAuth2 specs, the token is usually long-lived and may be used to obtain new access tokens. | +{{}} + + +## See Also {#see-also} + +- [NGINX Plus Native OIDC Module Reference documentation](https://nginx.org/en/docs/http/ngx_http_oidc_module.html) + +- [Release Notes for NGINX Plus R34]({{< ref "nginx/releases.md#r34" >}}) diff --git a/content/nginx/releases.md b/content/nginx/releases.md index 9fb127822..58dc8cdcf 100644 --- a/content/nginx/releases.md +++ b/content/nginx/releases.md @@ -25,7 +25,7 @@ _Based on NGINX Open Source 1.27.4_ NGINX Plus R34 is a feature release: -- OIDC authentication support via native [ngx_http_oidc_module](https://nginx.org/en/docs/http/ngx_http_oidc_module.html) module. +- [OIDC authentication support]({{< ref "nginx/admin-guide/security-controls/configuring-oidc.md" >}}) via native [ngx_http_oidc_module](https://nginx.org/en/docs/http/ngx_http_oidc_module.html) module. - NGINX usage reporting: [proxy](https://nginx.org/en/docs/ngx_mgmt_module.html#proxy) support. @@ -61,7 +61,7 @@ NGINX Plus R34 is supported on: - Amazon Linux 2 LTS is deprecated - SUSE Linux Enterprise Server 12 is removed - Ubuntu 20.04 is deprecated -- the [OpenTracing]({{< relref "nginx/admin-guide/dynamic-modules/opentracing.md" >}}) dynamic module is no longer available. It is recommended to use the [OpenTelemetry Distributed Tracing]({{< relref "nginx/admin-guide/dynamic-modules/opentelemetry.md" >}}) module, which incorporates all the features of the OpenTracing module. +- the [OpenTracing]({{< ref "nginx/admin-guide/dynamic-modules/opentracing.md" >}}) dynamic module is no longer available. It is recommended to use the [OpenTelemetry Distributed Tracing]({{< ref "nginx/admin-guide/dynamic-modules/opentelemetry.md" >}}) module, which incorporates all the features of the OpenTracing module. More information: [Announcing NGINX Plus R34](https://community.f5.com/kb/technicalarticles/f5-nginx-plus-r34-release-now-available/340300) diff --git a/static/nginx/images/oidc.png b/static/nginx/images/oidc.png new file mode 100644 index 0000000000000000000000000000000000000000..2e7e862706faa286a0f19713528233ad63cd7b5a GIT binary patch literal 108296 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%0AyK34=}zbDOfC!B_^hezpSK-h#zu6~>OKb^ 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 Dn6*%U literal 0 HcmV?d00001 From 25a6b4b4e9ca9ac434abc5a1a98cd27bb69cf119 Mon Sep 17 00:00:00 2001 From: yar Date: Tue, 1 Apr 2025 15:45:50 +0100 Subject: [PATCH 09/12] Apply suggestions from code review Co-authored-by: Mike Jang <3287976+mjang@users.noreply.github.com> --- .../security-controls/configuring-oidc.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/content/nginx/admin-guide/security-controls/configuring-oidc.md b/content/nginx/admin-guide/security-controls/configuring-oidc.md index f087db72e..2ce2e5ff4 100644 --- a/content/nginx/admin-guide/security-controls/configuring-oidc.md +++ b/content/nginx/admin-guide/security-controls/configuring-oidc.md @@ -9,7 +9,7 @@ weight: 550 This guide explains how to enable single sign-on (SSO) for applications being proxied by F5 NGINX Plus using: - OpenID Connect as the authentication mechanism -- an external Identity Provider (IdP) such as AD FS, Auth0, Cognito, Entra ID, Keycloak, OneLogin, Okta, Ping Identity and others +- An external Identity Provider (IdP) such as AD FS, Auth0, Cognito, Entra ID, Keycloak, OneLogin, Okta, Ping Identity and others - NGINX Plus as an OIDC client application that verifies user identity (Relying Party). OpenID Connect is an identity protocol that utilizes the authorization and authentication mechanisms of OAuth 2.0. With it, NGINX Plus can provide a layer of authentication for protected applications that do not natively support it. @@ -29,7 +29,7 @@ For the target client application, OIDC authentication can be enabled with great 4. The IdP redirects the user back to NGINX Plus with an authorization code. -5. NGINX Plus retrieves an `id_token` and access the token using the authorization code from the IdP. +5. NGINX Plus retrieves an `id_token` and access token using the authorization code from the IdP. 6. NGINX Plus validates the `id_token` and retrieves profile data for the user using the `UserInfo` endpoint. The retrieved profile data is validated and the content of the `id_token` and the profile data is used for providing access control to the client application. @@ -62,7 +62,7 @@ For the target client application, OIDC authentication can be enabled with great ## Prerequisites {#prerequisites} -- an Identity Provider application instance, either on-premises or in the cloud, with administrator privileges. +- An Identity Provider application instance, either on-premises or in the cloud, with administrator privileges. - An NGINX Plus [subscription](https://www.f5.com/products/nginx/nginx-plus) and NGINX Plus [Release 34](({{< ref "nginx/releases.md#r34" >}})) or later. For installation instructions, see [Installing NGINX Plus](https://docs.nginx.com/nginx/admin-guide/installing-nginx/installing-nginx-plus/). @@ -73,7 +73,7 @@ For the target client application, OIDC authentication can be enabled with great The workflow for each IdP provider is similar, but some steps may vary: -1. Log in to you IdP admin console. +1. Log in to your IdP admin console. 2. Create an OpenID Connect (OIDC) application. @@ -158,15 +158,15 @@ With your IdP configured, you can enable OIDC on NGINX Plus. NGINX Plus serves a 6. In the [`oidc_provider {}`](https://nginx.org/en/docs/http/ngx_http_oidc_module.html#oidc_provider) context, specify: - - your actual **Client ID** obtained from your IdP with the [`client_id`](https://nginx.org/en/docs/http/ngx_http_oidc_module.html#client_id) directive + - Your actual **Client ID** obtained from your IdP with the [`client_id`](https://nginx.org/en/docs/http/ngx_http_oidc_module.html#client_id) directive - - your **Client Secret** obtained from your IdP with the [`client_secret`](https://nginx.org/en/docs/http/ngx_http_oidc_module.html#client_secret) directive + - Your **Client Secret** obtained from your IdP with the [`client_secret`](https://nginx.org/en/docs/http/ngx_http_oidc_module.html#client_secret) directive - - the **Issuer** URL obtained from your IdP with the [`issuer`](https://nginx.org/en/docs/http/ngx_http_oidc_module.html#client_secret) directive + - The **Issuer** URL obtained from your IdP with the [`issuer`](https://nginx.org/en/docs/http/ngx_http_oidc_module.html#client_secret) directive By default, NGINX Plus creates the metadata URL by appending the `/.well-known/openid-configuration` part to the Issuer URL. If your Issuer is different, you can explicitly specify the metadata document with the [`config_url`](https://nginx.org/en/docs/http/ngx_http_oidc_module.html#config_url) directive. - - a valid system CA bundle with the [`ssl_trusted_certificate`](https://nginx.org/en/docs/http/ngx_http_oidc_module.html#ssl_trusted_certificate) so that NGINX Plus could validate the IdP TLS certificates: + - A valid system CA bundle with the [`ssl_trusted_certificate`](https://nginx.org/en/docs/http/ngx_http_oidc_module.html#ssl_trusted_certificate) so that NGINX Plus could validate the IdP TLS certificates: ```nginx http { @@ -230,7 +230,7 @@ With your IdP configured, you can enable OIDC on NGINX Plus. NGINX Plus serves a - [`$oidc_claim_name`](https://nginx.org/en/docs/http/ngx_http_oidc_module.html#var_oidc_claim_) - the full name of the user - - any other OIDC claim using the [`$oidc_claim_ `](https://nginx.org/en/docs/http/ngx_http_oidc_module.html#var_oidc_claim_) variable + - Any other OIDC claim using the [`$oidc_claim_ `](https://nginx.org/en/docs/http/ngx_http_oidc_module.html#var_oidc_claim_) variable ```nginx From 8dd1146d1db3ee34ff8959d380be3e4a0fed2d2b Mon Sep 17 00:00:00 2001 From: yar Date: Tue, 1 Apr 2025 15:55:59 +0100 Subject: [PATCH 10/12] Apply suggestions from code review Co-authored-by: Travis Martin <33876974+travisamartin@users.noreply.github.com> Co-authored-by: Mike Jang <3287976+mjang@users.noreply.github.com> --- .../admin-guide/security-controls/configuring-oidc.md | 7 ++++--- .../active-directory-federation-services.md | 8 ++++---- content/nginx/deployment-guides/single-sign-on/auth0.md | 8 ++++---- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/content/nginx/admin-guide/security-controls/configuring-oidc.md b/content/nginx/admin-guide/security-controls/configuring-oidc.md index 2ce2e5ff4..080bf3e6b 100644 --- a/content/nginx/admin-guide/security-controls/configuring-oidc.md +++ b/content/nginx/admin-guide/security-controls/configuring-oidc.md @@ -1,15 +1,16 @@ --- description: Enable OpenID Connect-based single sign-on (SSO) for applications proxied by NGINX Plus, using an Identity Provider (IdP). -doctypes: +type: - task title: Single Sign-On with OpenID Connect and Identity Providers toc: true weight: 550 +product: NGINX-PLUS --- This guide explains how to enable single sign-on (SSO) for applications being proxied by F5 NGINX Plus using: -- OpenID Connect as the authentication mechanism -- An external Identity Provider (IdP) such as AD FS, Auth0, Cognito, Entra ID, Keycloak, OneLogin, Okta, Ping Identity and others +- OpenID Connect as the authentication mechanism. +- An external Identity Provider (IdP) such as AD FS, Auth0, Cognito, Entra ID, Keycloak, OneLogin, Okta, Ping Identity and others. - NGINX Plus as an OIDC client application that verifies user identity (Relying Party). OpenID Connect is an identity protocol that utilizes the authorization and authentication mechanisms of OAuth 2.0. With it, NGINX Plus can provide a layer of authentication for protected applications that do not natively support it. diff --git a/content/nginx/deployment-guides/single-sign-on/active-directory-federation-services.md b/content/nginx/deployment-guides/single-sign-on/active-directory-federation-services.md index 775323273..96dc359ab 100644 --- a/content/nginx/deployment-guides/single-sign-on/active-directory-federation-services.md +++ b/content/nginx/deployment-guides/single-sign-on/active-directory-federation-services.md @@ -64,7 +64,7 @@ Check the OpenID Connect endpoint URL. By default, AD FS publishes the `.well-kn 1. Run the following `curl` command in a terminal: ```shell - curl https://adfs-server-address/adfs/.well-known/openid-configuration | jq + curl https://adfs-server-address/adfs/.well-known/openid-configuration | jq . ``` where: @@ -144,11 +144,11 @@ With AF DS configured, you can enable OIDC on NGINX Plus. NGINX Plus serves as t 6. In the [`oidc_provider {}`](https://nginx.org/en/docs/http/ngx_http_oidc_module.html#oidc_provider) context, specify: - - your actual AD FS **Client ID** from [Step 5](#adfs-setup-id) of AD FS Configuration with the [`client_id`](https://nginx.org/en/docs/http/ngx_http_oidc_module.html#client_id) directive + - Your actual AD FS **Client ID** from [Step 5](#adfs-setup-id) of AD FS Configuration with the [`client_id`](https://nginx.org/en/docs/http/ngx_http_oidc_module.html#client_id) directive - - your **Client Secret** from [Step 6](#adfs-setup-secret) of AD FS Configuration with the [`client_secret`](https://nginx.org/en/docs/http/ngx_http_oidc_module.html#client_secret) directive + - Your **Client Secret** from [Step 6](#adfs-setup-secret) of AD FS Configuration with the [`client_secret`](https://nginx.org/en/docs/http/ngx_http_oidc_module.html#client_secret) directive - - the **Issuer** URL from [Step 2](#adfs-setup-issuer) of AD FS Configuration with the [`issuer`](https://nginx.org/en/docs/http/ngx_http_oidc_module.html#client_secret) directive + - The **Issuer** URL from [Step 2](#adfs-setup-issuer) of AD FS Configuration with the [`issuer`](https://nginx.org/en/docs/http/ngx_http_oidc_module.html#client_secret) directive The `issuer` is typically your AD FS OIDC URL. By default, NGINX forms the provider metadata endpoint by appending `.well-known/openid-configuration` to the issuer. For AD FS, this often resolves to `https://adfs-server-address/adfs/.well-known/openid-configuration`. If your AD FS issuer differs from `https://adfs-server-address/adfs` (for example, a custom path), you can explicitly specify the metadata document with the [`config_url`](https://nginx.org/en/docs/http/ngx_http_oidc_module.html#config_url) directive. diff --git a/content/nginx/deployment-guides/single-sign-on/auth0.md b/content/nginx/deployment-guides/single-sign-on/auth0.md index 5937dc5a5..adb72a931 100644 --- a/content/nginx/deployment-guides/single-sign-on/auth0.md +++ b/content/nginx/deployment-guides/single-sign-on/auth0.md @@ -31,7 +31,7 @@ This guide explains how to enable single sign-on (SSO) for applications being pr 4. On the **Create application** screen: - - Enter the **Name** for the application, for example, **Nginx Demo App**. + - Enter the **Name** for the application, for example, **NGINX Demo App**. - In **Application Type**, select **Regular Web Applications**. @@ -135,11 +135,11 @@ With Auth0 configured, you can enable OIDC on NGINX Plus. NGINX Plus serves as t 6. In the [`oidc_provider {}`](https://nginx.org/en/docs/http/ngx_http_oidc_module.html#oidc_provider) context, specify: - - your actual Auth0 **Client ID** obtained in [Auth0 Configuration](#auth0-create) with the [`client_id`](https://nginx.org/en/docs/http/ngx_http_oidc_module.html#client_id) directive + - Your actual Auth0 **Client ID** obtained in [Auth0 Configuration](#auth0-create) with the [`client_id`](https://nginx.org/en/docs/http/ngx_http_oidc_module.html#client_id) directive - - your **Client Secret** obtained in [Auth0 Configuration](#auth0-create) with the [`client_secret`](https://nginx.org/en/docs/http/ngx_http_oidc_module.html#client_secret) directive + - Your **Client Secret** obtained in [Auth0 Configuration](#auth0-create) with the [`client_secret`](https://nginx.org/en/docs/http/ngx_http_oidc_module.html#client_secret) directive - - the **Issuer** URL obtained in [Auth0 Configuration](#auth0-create) with the [`issuer`](https://nginx.org/en/docs/http/ngx_http_oidc_module.html#client_secret) directive + - The **Issuer** URL obtained in [Auth0 Configuration](#auth0-create) with the [`issuer`](https://nginx.org/en/docs/http/ngx_http_oidc_module.html#client_secret) directive The `issuer` is typically your Auth0 OIDC URL. For Auth0, a trailing slash is included, for example: `https://yourTenantId.us.auth0.com/`. From c82d1934d3c1d0a8109fe504671de82dc1cb2dcc Mon Sep 17 00:00:00 2001 From: yar Date: Tue, 1 Apr 2025 16:26:48 +0100 Subject: [PATCH 11/12] Apply suggestions from code review Co-authored-by: Travis Martin <33876974+travisamartin@users.noreply.github.com> --- .../security-controls/configuring-oidc.md | 26 ++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/content/nginx/admin-guide/security-controls/configuring-oidc.md b/content/nginx/admin-guide/security-controls/configuring-oidc.md index 080bf3e6b..c9b4e2628 100644 --- a/content/nginx/admin-guide/security-controls/configuring-oidc.md +++ b/content/nginx/admin-guide/security-controls/configuring-oidc.md @@ -63,29 +63,31 @@ For the target client application, OIDC authentication can be enabled with great ## Prerequisites {#prerequisites} -- An Identity Provider application instance, either on-premises or in the cloud, with administrator privileges. +- An identity provider (IdP) set up on your network or in the cloud. You need admin access to the IdP. -- An NGINX Plus [subscription](https://www.f5.com/products/nginx/nginx-plus) and NGINX Plus [Release 34](({{< ref "nginx/releases.md#r34" >}})) or later. For installation instructions, see [Installing NGINX Plus](https://docs.nginx.com/nginx/admin-guide/installing-nginx/installing-nginx-plus/). +- An [NGINX Plus subscription](https://www.f5.com/products/nginx/nginx-plus) and NGINX Plus [Release 34]({{< ref "nginx/releases.md#r34" >}}) or later. + To install NGINX Plus, follow the steps in [Installing NGINX Plus](https://docs.nginx.com/nginx/admin-guide/installing-nginx/installing-nginx-plus/). -- A domain name pointing to your NGINX Plus instance, for example, `demo.example.com`. +- A domain name that points to your NGINX Plus instance (for example, `demo.example.com`). -## Configure your IdP {#idp-setup} +## Set up your identity provider (IdP) {#idp-setup} -The workflow for each IdP provider is similar, but some steps may vary: +The setup steps are similar for most identity providers, but some details may differ. -1. Log in to your IdP admin console. +1. Log in to your IdP's admin console. -2. Create an OpenID Connect (OIDC) application. +2. Create a new OpenID Connect (OIDC) application. - - Provide a name for the application - - Assign relevant users and groups that will require access + - Give the app a name. + - Add the users or groups who need access. -3. Obtain the Client ID and Client Secret in your IdP application. You will need them later when [configuring NGINX Plus as the Relying Party](#setup-oidc-provider2). +3. Find the **Client ID** and **Client Secret** for your app. + You'll need these later when you [set up NGINX Plus as the relying party](#setup-oidc-provider2). -4. Obtain the Issuer. +4. Find the **issuer** value. - The `issuer` value can be obtained from your IdP application or from the OpenID Connect Discovery URL provided by every IdP. Usually, the discovery URL is: + You can find the issuer value in your IdP app settings or at the standard discovery URL: `https://your-idp-domain/.well-known/openid-configuration` From 89990e1b58adf86f122f6c0cd2e415236c609932 Mon Sep 17 00:00:00 2001 From: yar Date: Tue, 1 Apr 2025 17:28:26 +0100 Subject: [PATCH 12/12] Apply suggestions from code review Co-authored-by: Travis Martin <33876974+travisamartin@users.noreply.github.com> --- .../single-sign-on/active-directory-federation-services.md | 5 +++-- content/nginx/deployment-guides/single-sign-on/auth0.md | 5 +++-- content/nginx/deployment-guides/single-sign-on/cognito.md | 5 +++-- content/nginx/deployment-guides/single-sign-on/entra-id.md | 5 +++-- content/nginx/deployment-guides/single-sign-on/keycloak.md | 5 +++-- .../deployment-guides/single-sign-on/oidc-njs/_index.md | 5 ++--- .../oidc-njs/active-directory-federation-services.md | 5 +++-- .../nginx/deployment-guides/single-sign-on/oidc-njs/auth0.md | 5 ++--- .../deployment-guides/single-sign-on/oidc-njs/cognito.md | 5 +++-- .../deployment-guides/single-sign-on/oidc-njs/keycloak.md | 5 +++-- .../nginx/deployment-guides/single-sign-on/oidc-njs/okta.md | 5 +++-- .../deployment-guides/single-sign-on/oidc-njs/onelogin.md | 5 ++--- .../single-sign-on/oidc-njs/ping-identity.md | 5 +++-- content/nginx/deployment-guides/single-sign-on/okta.md | 5 +++-- content/nginx/deployment-guides/single-sign-on/onelogin.md | 5 +++-- .../nginx/deployment-guides/single-sign-on/ping-identity.md | 5 +++-- 16 files changed, 45 insertions(+), 35 deletions(-) diff --git a/content/nginx/deployment-guides/single-sign-on/active-directory-federation-services.md b/content/nginx/deployment-guides/single-sign-on/active-directory-federation-services.md index 96dc359ab..ca12c8637 100644 --- a/content/nginx/deployment-guides/single-sign-on/active-directory-federation-services.md +++ b/content/nginx/deployment-guides/single-sign-on/active-directory-federation-services.md @@ -1,10 +1,11 @@ --- description: Enable OpenID Connect-based single sign-on (SSO) for applications proxied by NGINX Plus, using Microsoft AD FS as the identity provider (IdP). -doctypes: -- task +type: +- how-to title: Single Sign-On with Microsoft Active Directory FS toc: true weight: 300 +product: NGINX-PLUS --- This guide explains how to enable single sign-on (SSO) for applications being proxied by F5 NGINX Plus. The solution uses OpenID Connect as the authentication mechanism, with [Microsoft Active Directory Federation Services](https://docs.microsoft.com/en-us/windows-server/identity/active-directory-federation-services) (AD FS) as the Identity Provider (IdP) and NGINX Plus as the Relying Party (RP), or OIDC client application that verifies user identity. diff --git a/content/nginx/deployment-guides/single-sign-on/auth0.md b/content/nginx/deployment-guides/single-sign-on/auth0.md index adb72a931..987092ce1 100644 --- a/content/nginx/deployment-guides/single-sign-on/auth0.md +++ b/content/nginx/deployment-guides/single-sign-on/auth0.md @@ -1,7 +1,8 @@ --- description: Enable OpenID Connect-based single sign-on (SSO) for applications proxied by NGINX Plus, using Auth0 as the identity provider (IdP). -doctypes: -- task +type: +- how-to +product: NGINX-PLUS title: Single Sign-On With Auth0 toc: true weight: 100 diff --git a/content/nginx/deployment-guides/single-sign-on/cognito.md b/content/nginx/deployment-guides/single-sign-on/cognito.md index 272592132..c7d70a5c2 100644 --- a/content/nginx/deployment-guides/single-sign-on/cognito.md +++ b/content/nginx/deployment-guides/single-sign-on/cognito.md @@ -1,7 +1,8 @@ --- description: Enable OpenID Connect-based single sign-on (SSO) for applications proxied by NGINX Plus, using Amazon Cognito as the identity provider (IdP). -doctypes: -- task +type: +- how-to +product: NGINX-PLUS title: Single Sign-On with Amazon Cognito toc: true weight: 200 diff --git a/content/nginx/deployment-guides/single-sign-on/entra-id.md b/content/nginx/deployment-guides/single-sign-on/entra-id.md index 30056e47f..b9f445066 100644 --- a/content/nginx/deployment-guides/single-sign-on/entra-id.md +++ b/content/nginx/deployment-guides/single-sign-on/entra-id.md @@ -1,7 +1,8 @@ --- description: Enable OpenID Connect-based single sign-on (SSO) for applications proxied by NGINX Plus, using Microsoft Entra ID (formerly Azure Active Directory) as the identity provider (IdP). -doctypes: -- task +type: +- how-to +product: NGINX-PLUS title: Single Sign-On with Microsoft Entra ID toc: true weight: 400 diff --git a/content/nginx/deployment-guides/single-sign-on/keycloak.md b/content/nginx/deployment-guides/single-sign-on/keycloak.md index 6f4218f71..b0c17dfd1 100644 --- a/content/nginx/deployment-guides/single-sign-on/keycloak.md +++ b/content/nginx/deployment-guides/single-sign-on/keycloak.md @@ -1,7 +1,8 @@ --- description: Enable OpenID Connect-based single sign-on (SSO) for applications proxied by NGINX Plus, using Keycloak as the identity provider (IdP). -doctypes: -- task +type: +- how-to +product: NGINX-PLUS title: Single Sign-On with Keycloak toc: true weight: 500 diff --git a/content/nginx/deployment-guides/single-sign-on/oidc-njs/_index.md b/content/nginx/deployment-guides/single-sign-on/oidc-njs/_index.md index 3b7a3b79c..658ea11ab 100644 --- a/content/nginx/deployment-guides/single-sign-on/oidc-njs/_index.md +++ b/content/nginx/deployment-guides/single-sign-on/oidc-njs/_index.md @@ -1,9 +1,8 @@ --- description: Learn how to use OpenID Connect (OIDC) Provider Servers and Services to enable single sign-on for applications proxied by F5 NGINX Plus. -menu: - docs: - parent: NGINX Plus title: Legacy njs-based Single Sign-On Solutions weight: 990 +url: /nginx/deployment-guides/single-signon/oidc-njs/ +product: NGINX-PLUS --- diff --git a/content/nginx/deployment-guides/single-sign-on/oidc-njs/active-directory-federation-services.md b/content/nginx/deployment-guides/single-sign-on/oidc-njs/active-directory-federation-services.md index 80c776c47..5f6ff547c 100644 --- a/content/nginx/deployment-guides/single-sign-on/oidc-njs/active-directory-federation-services.md +++ b/content/nginx/deployment-guides/single-sign-on/oidc-njs/active-directory-federation-services.md @@ -2,11 +2,12 @@ description: Enable OpenID Connect-based single-sign for applications proxied by NGINX Plus, using Microsoft AD FS as the identity provider (IdP). docs: DOCS-463 -doctypes: -- task +type: +- how-to title: Single Sign-On with Microsoft AD FS and njs toc: false weight: 100 +product: NGINX-PLUS --- {{< note >}} This guide applies to NGINX Plus [Release 15]({{< ref "nginx/releases.md#r15" >}}) and later, based on the [`nginx-openid-connect`](https://github.com/nginxinc/nginx-openid-connect) GitHub repo. Starting with NGINX Plus [Release 34]({{< ref "nginx/releases.md#r34" >}}), use the simpler solution with the [native OpenID connect module](https://nginx.org/en/docs/http/ngx_http_oidc_module.html). diff --git a/content/nginx/deployment-guides/single-sign-on/oidc-njs/auth0.md b/content/nginx/deployment-guides/single-sign-on/oidc-njs/auth0.md index 4e192b0aa..71d7f6f26 100644 --- a/content/nginx/deployment-guides/single-sign-on/oidc-njs/auth0.md +++ b/content/nginx/deployment-guides/single-sign-on/oidc-njs/auth0.md @@ -2,13 +2,12 @@ description: Learn how to enable single sign-on (SSO) with [Auth0](https://auth0.com/) for applications proxied by F5 NGINX Plus. docs: DOCS-884 -doctypes: +type: - tutorial -tags: -- docs title: Single Sign-On With Auth0 and njs toc: false weight: 100 +product: NGINX-PLUS --- {{< note >}} This guide applies to NGINX Plus [Release 15]({{< ref "nginx/releases.md#r15" >}}) and later, based on the [`nginx-openid-connect`](https://github.com/nginxinc/nginx-openid-connect) GitHub repo. Starting with NGINX Plus [Release 34]({{< ref "nginx/releases.md#r34" >}}), use the simpler solution with the [native OpenID connect module](https://nginx.org/en/docs/http/ngx_http_oidc_module.html). diff --git a/content/nginx/deployment-guides/single-sign-on/oidc-njs/cognito.md b/content/nginx/deployment-guides/single-sign-on/oidc-njs/cognito.md index 19087ffd3..21a17b064 100644 --- a/content/nginx/deployment-guides/single-sign-on/oidc-njs/cognito.md +++ b/content/nginx/deployment-guides/single-sign-on/oidc-njs/cognito.md @@ -2,11 +2,12 @@ description: Enable OpenID Connect-based single-sign for applications proxied by NGINX Plus, using Amazon Cognito as the identity provider (IdP). docs: DOCS-464 -doctypes: -- task +type: +- how-to title: Single Sign-On with Amazon Cognito and njs toc: false weight: 100 +product: NGINX-PLUS --- {{< note >}} This guide applies to NGINX Plus [Release 15]({{< ref "nginx/releases.md#r15" >}}) and later, based on the [`nginx-openid-connect`](https://github.com/nginxinc/nginx-openid-connect) GitHub repo. Starting with NGINX Plus [Release 34]({{< ref "nginx/releases.md#r34" >}}), use the simpler solution with the [native OpenID connect module](https://nginx.org/en/docs/http/ngx_http_oidc_module.html). diff --git a/content/nginx/deployment-guides/single-sign-on/oidc-njs/keycloak.md b/content/nginx/deployment-guides/single-sign-on/oidc-njs/keycloak.md index ec72125fc..aca6e1cac 100644 --- a/content/nginx/deployment-guides/single-sign-on/oidc-njs/keycloak.md +++ b/content/nginx/deployment-guides/single-sign-on/oidc-njs/keycloak.md @@ -2,11 +2,12 @@ description: Enable OpenID Connect-based single-sign for applications proxied by NGINX Plus, using Keycloak as the identity provider (IdP). docs: DOCS-465 -doctypes: -- task +type: +- how-to title: Single Sign-On with Keycloak and njs toc: false weight: 100 +product: NGINX-PLUS --- {{< note >}} This guide applies to NGINX Plus [Release 15]({{< ref "nginx/releases.md#r15" >}}) and later, based on the [`nginx-openid-connect`](https://github.com/nginxinc/nginx-openid-connect) GitHub repo. Starting with NGINX Plus [Release 34]({{< ref "nginx/releases.md#r34" >}}), use the simpler solution with the [native OpenID connect module](https://nginx.org/en/docs/http/ngx_http_oidc_module.html). diff --git a/content/nginx/deployment-guides/single-sign-on/oidc-njs/okta.md b/content/nginx/deployment-guides/single-sign-on/oidc-njs/okta.md index d777a1481..4d1c81094 100644 --- a/content/nginx/deployment-guides/single-sign-on/oidc-njs/okta.md +++ b/content/nginx/deployment-guides/single-sign-on/oidc-njs/okta.md @@ -2,11 +2,12 @@ description: Learn how to enable single sign-on (SSO) with Okta for applications proxied by F5 NGINX Plus. docs: DOCS-466 -doctypes: -- task +type: +- how-to title: Single Sign-On with Okta and njs toc: false weight: 100 +product: NGINX-PLUS --- {{< note >}} This guide applies to NGINX Plus [Release 15]({{< ref "nginx/releases.md#r15" >}}) and later, based on the [`nginx-openid-connect`](https://github.com/nginxinc/nginx-openid-connect) GitHub repo. Starting with NGINX Plus [Release 34]({{< ref "nginx/releases.md#r34" >}}), use the simpler solution with the [native OpenID connect module](https://nginx.org/en/docs/http/ngx_http_oidc_module.html). diff --git a/content/nginx/deployment-guides/single-sign-on/oidc-njs/onelogin.md b/content/nginx/deployment-guides/single-sign-on/oidc-njs/onelogin.md index dbe3b4d4f..1048acced 100644 --- a/content/nginx/deployment-guides/single-sign-on/oidc-njs/onelogin.md +++ b/content/nginx/deployment-guides/single-sign-on/oidc-njs/onelogin.md @@ -2,13 +2,12 @@ description: Learn how to enable single sign-on (SSO) with [OneLogin](https://www.onelogin.com/) for applications proxied by F5 NGINX Plus. docs: DOCS-467 -doctypes: +type: - tutorial -tags: -- docs title: Single Sign-On with OneLogin and njs toc: false weight: 100 +product: NGINX-PLUS --- {{< note >}} This guide applies to NGINX Plus [Release 15]({{< ref "nginx/releases.md#r15" >}}) and later, based on the [`nginx-openid-connect`](https://github.com/nginxinc/nginx-openid-connect) GitHub repo. Starting with NGINX Plus [Release 34]({{< ref "nginx/releases.md#r34" >}}), use the simpler solution with the [native OpenID connect module](https://nginx.org/en/docs/http/ngx_http_oidc_module.html). diff --git a/content/nginx/deployment-guides/single-sign-on/oidc-njs/ping-identity.md b/content/nginx/deployment-guides/single-sign-on/oidc-njs/ping-identity.md index 9acf0068c..3999d9a0e 100644 --- a/content/nginx/deployment-guides/single-sign-on/oidc-njs/ping-identity.md +++ b/content/nginx/deployment-guides/single-sign-on/oidc-njs/ping-identity.md @@ -2,8 +2,9 @@ description: Enable OpenID Connect-based single-sign for applications proxied by NGINX Plus, using Ping Identity as the identity provider (IdP). docs: DOCS-468 -doctypes: -- task +type: +- how-to +product: NGINX-PLUS title: Single Sign-On with Ping Identity and njs toc: false weight: 100 diff --git a/content/nginx/deployment-guides/single-sign-on/okta.md b/content/nginx/deployment-guides/single-sign-on/okta.md index 24720d14e..9c976326a 100644 --- a/content/nginx/deployment-guides/single-sign-on/okta.md +++ b/content/nginx/deployment-guides/single-sign-on/okta.md @@ -1,7 +1,8 @@ --- description: Enable OpenID Connect-based single sign-on (SSO) for applications proxied by NGINX Plus, using Okta as the identity provider (IdP). -doctypes: -- task +type: +- how-to +product: NGINX-PLUS title: Single Sign-On with Okta toc: true weight: 700 diff --git a/content/nginx/deployment-guides/single-sign-on/onelogin.md b/content/nginx/deployment-guides/single-sign-on/onelogin.md index c3c2091ac..99f323b49 100644 --- a/content/nginx/deployment-guides/single-sign-on/onelogin.md +++ b/content/nginx/deployment-guides/single-sign-on/onelogin.md @@ -1,7 +1,8 @@ --- description: Enable OpenID Connect-based single sign-on (SSO) for applications proxied by NGINX Plus, using OneLogin as the identity provider (IdP). -doctypes: -- task +type: +- how-to +product: NGINX-PLUS title: Single Sign-On with OneLogin toc: true weight: 600 diff --git a/content/nginx/deployment-guides/single-sign-on/ping-identity.md b/content/nginx/deployment-guides/single-sign-on/ping-identity.md index 804cc55e3..92ed75600 100644 --- a/content/nginx/deployment-guides/single-sign-on/ping-identity.md +++ b/content/nginx/deployment-guides/single-sign-on/ping-identity.md @@ -1,7 +1,8 @@ --- description: Enable OpenID Connect-based single sign-on (SSO) for applications proxied by NGINX Plus, using Ping Identity as the identity provider (IdP). -doctypes: -- task +type: +- how-to +product: NGINX-PLUS title: Single Sign-On with Ping Identity toc: true weight: 800