Implement JWT Authorization

Overview

In this tutorial, you are going to learn how to configure a web application to create a JWT token and send it to Qlik Cloud to authorize a user to view embedded content from a Qlik Sense application.

Note: This tutorial focuses on the authorization flow for working with JWTs and Qlik Cloud. While it demonstrates the process through coded examples in JavaScript, it is not plug-and-play code.

Contents:

  • Before you begin
  • Configure a web integration id
  • Configure JWT authorization project
    • Connection to Qlik Cloud
    • Token configuration
  • Configure Content Security Policy for embedding iframes

Before you begin

  • JWT authorization capabilities in Qlik Cloud are available on an "as requested" basis for embedded analytics use cases on Enterprise SaaS tenants only. Contact your Qlik account team with a description of your use case requiring JWT capability on your tenant before continuing this tutorial.

  • Only a user with the Tenant Admin role may access the management console settings and configure JWT identity provider settings. If you aren't the Tenant Admin of your Qlik Cloud tenant, contact your Tenant Admin and direct them to this tutorial.

  • Please complete the JWT identity provider configuration in your Qlik Cloud tenant before continuing this tutorial. You may learn how to configure your tenant to use JWT authorization here.

  • This tutorial is based upon an example custom web application written with node.js. This tutorial does not provide instructions on how to adapt this code to your own custom web application. The intent of the tutorial is explain the authentication flow required to use JWT authorization capabilities with Qlik Cloud. The example code reference in this tutorial is available as a live example on glitch.com.

  • Make sure to have the following information at the ready before completing this tutorial:

    • The keyid value for the JWT identity provider configuration
    • The issuer value for the JWT identity provider configuration
    • A copy of the private key used for signing JWTs sent to Qlik Cloud. This private key pairs with the public key used for configuring the JWT identity provider.
    • The fully qualified domain name of the Qlik Cloud tenant where the JWT identity provider configuration is set.

Configure a web integration id

Web integrations are a security mechanism allowing websites that present a valid id associated with an URL inclusion list the capability to render embedded visualizations.

  1. Open a web browser and navigate to your Qlik Cloud tenant. Once authenticated to your tenant, click the waffle icon and select Management Console.

    a screenshot of the waffle menu of Qlik Cloud.
  2. Select Web in the Integration section of the side menu. Click the Create new button on the right side of the screen.

    a screenshot of the mc menu of Qlik Cloud.
  3. In the configuration window, give the web integration a name, and provide the URL for the web applications containing the references to embedded visualizations.

    a screenshot of the waffle menu of Qlik Cloud.
  4. Press the Add button to add the URL - also known as an origin - to the Allowed origins list. Then click the Create button.

  5. Copy the generated web integration id and put it aside for use in your project later on in this tutorial.

    a screenshot of the waffle menu of Qlik Cloud.

Configure JWT authorization project

This project uses the qlik-cloud-jwt boilerplate repository to show you how to implement JWT authorization in your web application.

Project directory structure

.
+-- README.md
+-- package.json
+-- config
|   +-- config.js # configuration file of references for Qlik tenant connection
+-- data
|   +-- private.key.txt # JWT signing certificate
+-- token
|   +-- token.js # JWT creation code
+-- src
|   +-- app.js # code for showing content for demo purposes
|   +-- auth.js # the authorization flow
|   +-- configuration.js # code for defining nebula visualization
|   +-- connectQlikApp.js # code for websocket connection to Qlik
|   +-- index.html # html for demo purposes
|   +-- server.js # backend for serving code for demo purposes
|   +-- styles.css # css for demo purposes

This tutorial focuses on the config.js, token.js, and auth.js files so that you learn the authorization flow for working with JWTs and Qlik Cloud.

Configure config.js

Begin setting up your project with a config.js file. In this file, you set the attributes that are used in the rest of the project to make it go.

The boilerplate starts with definitions for the different log in types available on Qlik Cloud. This is nice to have as you troubleshoot your code because you can switch between forcing an interactive log in - logging in with a userId and password - and using the JWT token you pass through to the tenant.

  • Create a config.js file. Add the loginTypes object to the file if you want to toggle between interactive and JWT based authentication experiences.

    const loginTypes = {
      INTERACTIVE_LOGIN: 'interactive-login',
      JWT_LOGIN: 'jwt-login'
    }
  • Add loginTypes and currentLoginType to a new JSON object if you plan to use them.

    This next part of the config.js file is the section where you add information about your tenant, the Qlik Sense application id you want to embed content from,and the JWT configuration settings from your tenant. In addition, this is where you set the currentLoginType to use.

    Set the currentLoginType property to the log in you want the project to support.

    module.exports = {
      loginTypes,
      currentLoginType: loginTypes.JWT_LOGIN,
  • Create the qlikWebIntegrationId property and add the web integration id you obtained in the Configure a web integration id section.

      qlikWebIntegrationId: "ENTER_WEB_INTEGRATION_ID_HERE",
  • Add the tenantDomain property and provide the hostname of your Qlik Cloud tenant. You can find the hostname of your tenant by logging into Qlik Cloud and reviewing the address bar in the browser.

      tenantDomain: "example.region.qlikcloud.com",
  • Add the appId property and supply the GUID found in the URL.

    Embedding content from a Qlik Sense application requires knowing the unique identifier for that app on your Qlik Cloud tenant. The easiest way to obtain this appId is to sign in to your tenant and open the app. The app id appears in the address bar of the browser https://tenant.region.qlikcloud.com/sense/app/04c0e6e0-b979-4956-b65d-2122082127a1/overview as the GUID in between app and overview.

      appId: "bc97609f-523b-4fe9-91f6-78aa0bd1b989",
  • Next create properties for the issuer and keyid from the JWT identity provider configuration you established because you read the tutorial here before this one.

      // token config
      issuer: "ISSUER_VALUE_FROM_JWT_IDP_CONFIGURATION",
      keyid: "KEYID_VALUE_FROM_JWT_IDP_CONFIGURATION"
  • The completed config.js file should look like this:

    module.exports = {
      loginTypes,
      currentLoginType: loginTypes.AUTOMATIC_LOGIN,
    
      // app config
      qlikWebIntegrationId: "ENTER_WEB_INTEGRATION_ID_HERE",
      tenantDomain: "example.region.qlikcloud.com",
      appId: "bc97609f-523b-4fe9-91f6-78aa0bd1b989",
    
      // token config
      issuer: "ISSUER_VALUE_FROM_JWT_IDP_CONFIGURATION",
      keyid: "KEYID_VALUE_FROM_JWT_IDP_CONFIGURATION"
    };

Add the private key file to the project

The private key you generated from the tutorial Create Signed Tokens for JWT Authorization is needed in the project to sign the token you are going to create in the next section. Place the private key file into a folder named .data.

If you are using a remix of the qlik-cloud-jwt boilerplate on Glitch the data folder is a hidden folder you have to reference whenever you want to add a file to it. This is so when projects are remixed, the contents of the data folder do not remix along with the rest of the project.

See the appendix here to learn how to add your private key to the .data folder.

Configuring the token.js file

The token.js module is the code for generating the JWT (JSON Web Token) used to authorize users to Qlik Cloud. The code requires the config.js file and reads the private key so it can sign the JWT and ensure the data inside is verifiable by the JWT identity provider configuration. Signing the JWT is handled by the jsonwebtoken module found on npm.

Here are the lines that import those components into token.js.

  const jsonWebToken = require("jsonwebtoken");
  const fs = require("fs");
  const config = require("../config/config");

  const key = fs.readFileSync(".data/private.key.pem", "utf8");

The generate function does all the heavy lifting in this file and there are two important variables to make note of; the signingOptions and the payload.

The signingOptions variable is a JSON object providing the information used to verify the JWT and how long the JWT can be used.

For Qlik Cloud to validate the JWT, the signing options must include all the properties in the code below. The keyid and issuer come from the JWT identity provider configuration. The algorithm must be set to "RS256" and the audience must be set to "qlik.api/login/jwt-session".

The expiresIn property defines how long the JWT is valid and can be used for processing authorization. This property has no bearing on the expiration of the session cookie Qlik Cloud returns to provide access to content. Keep the expiresIn duration short to mitigate the risk of token reuse from an unauthorized source.

Here is an example of a valid signingOptions object.

    const signingOptions = {
      keyid: config.keyid,
      algorithm: "RS256",
      issuer: config.issuer,
      expiresIn: "30s",
      audience: "qlik.api/login/jwt-session"
    };

The payload variable is a JSON object containing information describing the user seeking authorization to Qlik Cloud.

The required claims for a Qlik JWT payload are the following:

  • sub - The main identifier (aka subject) of the user.
  • subType - The type of identifier the sub represents. In this case, user is the only applicable value.
  • name - The friendly name to apply to the user.
  • email - The email address of the user.
  • email_verified - A claim indicating to Qlik that the JWT source has verified that the email address belongs to the subject.

It's possible to send additional claims in the JWT. Today, only the optional claim groups is read.

Here is a sample JWT payload including the groups attribute:

{
   "sub": "SomeSampleSeedValue", //e.g. 0hEhiPyhMBdtOCv2UZKoLo4G24p-7R6eeGdZUQHF0-c
   "subType": "user",
   "name": "Hardcore Harry",
   "email": "harry@example.com",
   "email_verified": true,
   "groups": ["Administrators", "Sales", "Marketing"]
}

In the token.js code, the generate function requires sending in the sub, name, email, and groups as an array.

  const payload = {
    sub: sub,
    subType: "user",
    name: name,
    email: email
    email_verified: true,
    groups: groups
  };

With the signing options and the payload configured, use jsonWebToken.sign supplying it with both objects and the private key to generate the JWT token. Then return the token so that it can be used to authorize the use to Qlik Cloud.

  const token = jsonWebToken.sign(payload, key, signingOptions);
  return token;

auth.js file review

The auth.js file sends the authorization token to Qlik Cloud and obtains the required response information to render content. The code works this way:

  • Gets the configuration information from config.js to begin the authorization process.

    const { tenantDomain, qlikWebIntegrationId, appId, currentLoginType, loginTypes } = await fetch("config").then((resp) => resp.json());
  • Checks which authentication and authorization method to use; interactive or JWT.

    if(currentLoginType === loginTypes.JWT_LOGIN) await handleAutomaticLogin()
    else if (currentLoginType === loginTypes.INTERACTIVE_LOGIN) await handleUserLogin()
  • Performs the appropriate log in based on the method set in config.js.

    • In JWT_LOGIN mode, the code requests a token and performs the authorization with Qlik Cloud.

      async function handleAutomaticLogin() {
        const { token } = await fetch("token").then(resp =>
        resp.json());
      
        const login = await fetch(
          `https://${tenantDomain}/login/jwt-session?qlik-web-integration-id=${qlikWebIntegrationId}`,
          {
            method: "POST",
            credentials: "include",
            mode: "cors",
            headers: {
              "content-type": "application/json",
              Authorization: `Bearer ${token}`,
              "qlik-web-integration-id": qlikWebIntegrationId
            },
            rejectunAuthorized: false
          }
        );
      }
    • In INTERACTIVE_LOGIN mode, the code redirects the browser to the log in page for the tenant. Upon successful authentication, Qlik Cloud redirects back to the web application.

      async function handleUserLogin() {
          const response = await fetch(`https://${tenantDomain}/api/v1/csrf-token`, {
            credentials: 'include',
            headers: { 'qlik-web-integration-id': qlikWebIntegrationId }
          })
      
          if(response.status === 401) {
            shouldLoginBox.style.display = 'block'
      
            const loginUrl = new URL(`https://${tenantDomain}/login`);
            loginUrl.searchParams.append('returnto', window.location.href);
            loginUrl.searchParams.append('qlik-web-integration-id', qlikWebIntegrationId);
      
            loginLink.href = loginUrl.href;
          }
        }
  • Before requesting any content from Qlik Cloud, the web application needs to obtain a cross-site request forgery token. The CSRF token acts as an additional handshake between the web application and Qlik Cloud that requests coming from the web application are valid.

    const csrfTokenInfo = await (await fetch(
        `https://${tenantDomain}/api/v1/csrf-token?qlik-web-integration-id=${qlikWebIntegrationId}`,
        {
          credentials: "include",
          headers: {
            "Qlik-Web-Integration-ID": qlikWebIntegrationId
          }
        }
      ));

    Note: The code above requires the double use of await to ensure the browser completes the preflight request to the csrf-token endpoint and the GET request to return the cross-site scripting token.

It's now possible to make a request for content from Qlik Cloud by including the web integration id and CSRF token in the URL. Refer to the qlik-cloud-jwt project on Glitch and review the app.js and connectQlikApp.js files.

Configuring Qlik Cloud to allow iframe embedding in your web application

If the solution includes embedding Qlik Sense visualizations using iframe technology, the tenant hosting the analytic content must have an entry added to the Content Security Policy section in the management console.

  1. Open a web browser and navigate to your Qlik Cloud tenant. Once authenticated to your tenant, click the waffle icon and select Management Console.

    a screenshot of the management console menu of Qlik
Cloud.

Content Security Policy (CSP) is a browser mechanism for mitigating and preventing cross-site scripting (XSS) attacks. Setting a CSP header in the management console enables Qlik components embedded in external web applications to render and work properly. 2. Select Content Security Policy in the Integration section of the side menu. Click the Add button on the right side of the screen.

a screenshot of the add configuration button of Qlik Cloud.3. In the Add origin window, give the entry a name and provide the origin for the web application, for exmaple `glitch.com`). Under the origin, select the `frame-ancestors` directive.a screenshot of the csp configuration screen of Qlik Cloud.

By selecting the frame-ancestors directive, the web application with the iframe tag containing a reference to Qlik Cloud renders the requested content.

For more information on content security policy, visit this website on the topic.

Conclusion

The aim of this tutorial is to help provide understanding for configuring JWT authorization to Qlik Cloud through a web application. To experience a complete example, please visit the project qlik-cloud-jwt.

Was this page helpful?