Embed content using iframes and anonymous access

qlik-embed: Where possible, use qlik-embed rather than this framework. Review the tutorial embedding Qlik Analytics using qlik-embed web components.

Third-party cookies: This tutorial leverages cookies for auth, which are blocked by some browser vendors. Please use an OAuth solution where possible, which doesn’t leverage cookies.

Contributed by Daniel Pilla

Overview

A common use case for analytics is displaying visualizations and supplying datasets through public-facing websites and portals. An auto-scaling cloud platform makes it possible for you to focus on the experience you want to deliver instead of managing hardware to ensure scale. In this tutorial, you are going to learn how to implement anonymous access to surface embedded analytics from Qlik Cloud.

By default, Qlik Cloud requires users to be known to the platform before authorizing them to the content. You can use JSON web tokens (JWT) to authorize temporary, single-access users to surface this content without prompting for authentication.

Generating JWTs requires code on the backend of your application for security purposes and access control. This guide uses a serverless AWS Lambda function written in Python to secure JWT request and response operations. In a production deployment, you may choose a different language or tech stack to handle generating JWTs. If you prefer node.js, read this tutorial on JWT authorization.

Note: To provide anonymous access to Qlik Cloud, your tenant must have an enterprise entitlement with analyzer capacity minutes and must have the JWT entitlement. As discussed previously, implementing JWT authorization requires implementing middleware to create the JWTs representing temporary users.

In addition, remember this is sample code. The intent of this guide is to demonstrate how to authorize users with JWTs to Qlik Cloud using AWS Lambda. Make sure to apply the appropriate security measures to protect your production implementation, or consult your IT organization for support to take your code to production.

Level of complexity

This tutorial is advanced and requires experience with web application development, AWS Lambda, and Qlik Cloud.

Prerequisites

A Qlik Cloud tenant for anonymous use

It is highly recommended to have a dedicated Qlik Cloud tenant for anonymous access. A dedicated tenant allows you to isolate the anonymous use case and control tenant-level features. Anonymous users should not have the ability to create automations, notes, and so on. All features should be toggled off under Management ConsoleConfigurationSettingsFeature control with one exception. Creation of groups must be enabled to grant anonymous access to the Space where the embedded content resides.

Note: Ensure that you also have turned off Enable dynamic assignment of professional users and Enable dynamic assignment of analyzer users so anonymous users use Analyzer Capacity minutes and do not assign entitlements from your named user pool.

OpenSSL for creating signing certificates

Using JWT authorization on Qlik Cloud requires certificates to sign the JWTs your application is going to send to validate the auth request. An example for this tutorial is included below, however a detailed article also exists here here for further reading.

Note: This tutorial uses a smaller certificate keypair size than the referenced tutorial demonstrates. This tutorial uses a 2048 bit keypair instead of a 4096 bit keypair so that the private key can fit inside of an AWS Lambda environmental variable. A 4096 bit keypair is preferable in a production deployment.

An AWS account for using Lambda

If you are using the provided AWS Lambda functions, you need an AWS account with the ability to create an AWS Lambda function.

Variable substitution and vocabulary

Throughout this tutorial, variables will be used to communicate value placement. The variable substitution format is <VARIABLE_NAME>. Here is a list of variables that appear in the sample code.

VariableDescription
<TENANT>The domain for the tenant you are accessing. Equivalent to tenant.region.qlikcloud.com.
<TENANT_ID>The unique identifier of the tenant.
<WEB_INTEGRATION_ID>The unique identifier of the web integration.
<APP_ID>The unique identifier of the application that you will be embedding.
<SHEET_ID>The unique identifier of the sheet that you will be embedding.
<ISSUER>The Issuer value from the JWT configuration, found under IdP settings.
<KID>The Key ID from the JWT configuration, found under IdP settings.
<PRIVATE_KEY>The base64-encoded private key.
<FUNCTION_URL>The URL of your AWS Lambda function for the JWT generation.
<API_KEY>Tenant administrator API key.

Files

This tutorial consists of the following components:

ComponentDescriptionFile
Qlik Cloud TenantA dedicated Qlik Cloud tenant for anonymous access.N/A
AWS Lambda FunctionThe Lambda function for generating the JWT.generate_jwt_token.zip
AWS Lambda FunctionThe Lambda function for removing old users.remove_old_users.zip
HTML File (web page)The sample web page that embeds an iframe using JWT.index.html

Note: Hosting and serving the HTML file (web page) is not covered in this tutorial. Embedding content hosted on Qlik Cloud requires that the page is served over https.

How anonymous access works

  1. The anonymous user accesses the site with the embedded content from the Qlik Cloud tenant.
  2. The site makes a request to verify if the user is currently signed in to the Qlik Cloud tenant.
  3. If there is no existing session to the Qlik Cloud tenant, the site requests a JWT from the AWS Lambda function.
  4. The site accepts the token and requests a session cookie from the Qlik Cloud tenant’s JWT session endpoint.
  5. The user receives a cookie authorizing the site to render the content from the Qlik Cloud tenant.
  6. Periodically, another AWS Lambda function purges expired temporary users from the Qlik Cloud tenant.

Note: Once the browser has a session cookie from the Qlik Cloud tenant, the application will not request another JWT unless the user closes the browser page, the session expires, or the temporary user is purged.

Configuration

Step 1 - Configure tenant

The code example in this tutorial provides a basic, fully functional web page that renders embedded content in an iframe after successful authorization to Qlik Cloud with a JWT.

Configure embedding requirements

  1. Within the Management Console:
    1. Under the Settings side bar section, find the tenant ID and record it for later use.
    2. Under the Integration sidebar section, configure a web integration that contains the location of the web page that will be embedding the Qlik Cloud application. Record the web integration ID for later use.

      Note: Ensure that your web page is served over https.

    3. Under the Integration sidebar section, configure a content security policy entry with the frame-ancestors directive added for the location of the web page that will be embedding the Qlik Cloud application. This is the fully qualified domain of your web application.
  2. Obtain an existing application ID and published sheet ID to embed. Record them for later use. Ensure that this application resides in a managed or shared space that you will grant anonymous access to via a group in a later step.

Configure JWT IdP

  1. Begin by generating a signing certificate keypair (public and private key).

    openssl genrsa -out private-key.pem 2048
    openssl rsa -in private-key.pem -pubout -out public-key.pem
    

    Note: Using a keypair size greater than 2048 bytes may require you to modifiy the example AWS Lambda Function because the keypair string is larger than AWS Lambda environment variables can support. Refer to the Appendix— Using a larger keypair section below if you plan to use a larger keypair.

  2. Within the Management Console under the Configuration sidebar section, select Identity provider, and then Create new.

  3. Under Type, select JWT.

  4. Copy the entire contents of the public-key.pem file and paste it into the Certificate section.

  5. The Issuer and Key ID values will be generated automatically when you click the Create button.

  6. Record the generated issuer and key id values for later use.

Step 2 - Configure AWS Lambda function

Create the function

  1. Create a new AWS Lambda function.
  2. Provide a name for the function.
  3. Select Python 3.7 as the runtime.
  4. Select x86_64 as the architecture.
  5. The execution role can be left as default (which is Create a new role with basic Lambda permissions). Note that the AWS Lambda function will not have to interact with any other AWS services, so there are no additional policy modifications that need to be made. You can use an existing basic Lambda role here if you already have one.

Configure the function

For example purposes, the private key used in the function is encoded to base64 and placed into an environment variable within the AWS Lambda function configuration. In a production deployment, it is recommended that you store the private key in a more secure manner, such as in AWS Secrets Manager, fetching it programmatically via AWS Lambda at run time.

  1. base64 encode the private key:

    On Windows with PowerShell:

    $cert = Get-Content "private-key.pem" -Encoding UTF8 -Raw
    [System.Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($cert))
    

    On Mac or Linux:

    base64 privatekey.pem
    

    The output should resemble LS0tLS1CR.... Record this value for later use. Ensure that there are no leading or trailing spaces.

  2. Download the example AWS Lambda function from here.

  3. Navigate to your AWS Lambda function in the console. Under the Code tab, click the Upload from drop-down menu and select .zip file.

  4. Navigate to the location on your local file system where you downloaded the file from step 1 and upload it.

  5. Once uploaded, click the Configuration tab, then click the General configuration tab in the sidebar. Finally, click Edit.

  6. Find the Timeout setting, and set it to 10 seconds. The default is typically 3 seconds, which should be enough time, but it is good to have the extra padding just in case. Click Save.

  7. While remaining on the Configuration tab, click the Environmental variables tab in the sidebar, then click Edit.

  8. Create the following environment variables and add their respective values:

    1. issuer
    2. kid
    3. private_key

    Note: If you receive an error when saving the variables that they are too large (over 4 KB, refer to Lambda Environment Variable Size), this is due to the size of your private key. You can either go back to the Configure JWT IdP section of the tutorial and create a 2048 bit keypair, or if you plan on using a larger private key, refer to the Appendix— Using a larger keypair section below and save only the issuer and kid environment variables in this step.

  9. While remaining on the Configuration tab, select Function URL, and then select Create function URL.

    Note: This tutorial takes advantage of a Lambda Function URL which allows you to invoke the function directly via REST without the need for other AWS services such as API Gateway.

    Under AuthType, select NONE, as this RESTful endpoint needs to be accessed anonymously. In addition, select Configure cross-origin resource sharing (CORS). Click Save.

    Note: The default CORS configuration is set to *, which allows access to the AWS Lambda Function URL from any domain. After testing, it is advised to set this to the domain of your embedded mashup so only that domain can make requests. There are also additional controls that can be added like only allowing certain methods. Refer to the AWS documentation.

  10. You will now see a Function URL exposed to call the function directly. Record this value for later use. Click on the URL to open a new window and expose the output of your Lambda function. If successful, the result should resemble the following output:

    { "body": "ey..." }
    
  11. Copy the token (ey...) from the response and paste it into jwt.io to take a look at the contents. The JWT should resemble the following:

    {
      "sub": "ANON\\fa723e6c-d655-11ec-9540-aa02d90848bd",
      "name": "Anonymous",
      "email": "fa723e6c-d655-11ec-9540-aa02d90848bd@example.com",
      "email_verified": true,
      "iss": "<OBFUSCATED>",
      "nbf": 1652842230,
      "exp": 1652846130,
      "jti": "<OBFUSCATED>",
      "aud": "qlik.api/login/jwt-session",
      "groups": ["anonymous"]
    }
    

Step 3 - Configure web page variables

Note: The sample web page code includes handling for potential third-party cookie issues. In addition, the code includes the ability to catch a session disconnect and/or suspension utilizing a parallel session with enigma.js. Refer to Managing iframe embedded content session state with enigma.js where you can find more information.

  1. Download the example web page from here.

  2. Configure the sample web page by filling in the following variables in the HTML file. All the variables should be in the notes you’ve made throughout this tutorial.

  const TENANT = '<TENANT>';
  const JWTENDPOINT = '<FUNCTION_URL>';
  const WEBINTEGRATIONID = '<WEB_INTEGRATION_ID>';
  const APPID = '<APP_ID>';
  const SHEETID = '<SHEET_ID>';

Pseudocode

The code proceeds through the following sequence:

  1. Handles logging into the Qlik Cloud tenant with JWT.
  2. Fetches the CSRF token so that a WebSocket connection can be made to a Qlik Cloud engine.
  3. Connects to the Qlik application leveraging enigma.js.
  4. Establishes listeners on the WebSocket session closed and suspended events.
  5. Fetches the active theme of the application (this is purely bonus code showing the power of the Engine).
  6. Renders the iframe.

Step 4 - Configure group access

The JWT, which is created automatically, sends the group anonymous in with all users. In order for this group to be added to the Qlik Cloud tenant so that it can be assigned to a space, a user must first sign in with this group added to their JWT.

Note: Ensure that you have the Creation of groups feature toggled on under Management ConsoleConfigurationSettingsFeature control, as described in the Prerequisites section.

  1. Close all instances of your current browser and open a new browser window.
  2. Open up the web page that hosts the embedded content from Qlik Cloud. The web page will not yet be able to render the content, as the user does not yet have access to the app. The sign in process however will inject the group into the Qlik Cloud tenant.
  3. Once again close your browser or open up a separate browser and sign in to the Qlik Cloud tenant as a TenantAdmin.
  4. Navigate to the Hub, select Catalog and navigate to the space where the embedded app resides.
  5. Select Manage space and then Members.
  6. Select Add members.
  7. Search for anonymous in lowercase. You will see an icon that resembles two people, which denotes a group. Select that group.
  8. Keep the selection as Can view only, and select Add members.

The space is now configured to allow anonymous users access to the Qlik Sense applications within it.

Step 5 - Confirm embedded anonymous access

  1. Close all instances of your current browser and open a new browser window. Do not use Chrome Incognito or another browser’s private mode because third-party cookies are required for access.
  2. Open up the web page that hosts the embedded content from Qlik Cloud.
  3. Ensure the web page properly loads the iframe anonymously.

Step 6 - Manage user cleanup

As a new user is created in Qlik Cloud each time a user accesses the application from a fresh browser launch or session expiry, it is recommended that you add a user cleanup process. There are several ways to automate user cleanup. This tutorial includes an additional AWS Lambda function template, or alternatively, you could do this with qlik-cli or otherwise. Regardless, it is important to keep your tenant clean and tidy.

Note: For both of the options below, you will need to make sure that you have an API Key. Record this value once you have it for use below.

Option 1 - User removal Lambda function

  1. Create a new AWS Lambda function.

  2. Provide a name.

  3. Select Python 3.7 as the runtime.

  4. Select x86_64 as the architecture.

  5. The execution role can be left as default, which is Create a new role with basic Lambda permissions. Note that the AWS Lambda function will not have to interact with any other AWS services, so there are no additional policy modifications that need to be made. You can use an existing basic Lambda role here if you already have one.

  6. Download the example AWS Lambda function from here.

  7. Under the Code tab, click the Upload from drop-down menu and select .zip file. Navigate to the location of the downloaded file and upload it.

  8. Once uploaded, click the Configuration tab, then click the General configuration tab in the sidebar. Finally, click Edit.

  9. Find the Timeout setting, and set it to 3 minutes and 0 seconds. The default is typically 3 seconds, which is not enough time to potentially delete hundreds or thousands of users. Depending on the cadence in which you execute this function, you may have to adjust this time accordingly. Three minutes should give ample overhead, considering that the deletes are done asynchronously in batches. Click Save.

  10. While remaining on the Configuration tab, click the Environmental variables tab in the sidebar, then click Edit.

  11. Create the following variables with their respective values:

    1. api_key: <Qlik TenantAdmin API Key>
    2. match_user_subject_pattern: ANON\
    3. tenant: <tenant FQDN, e.g. acme.us.qlikcloud.com>
    4. tenant_id: <tenant ID, found under Settings in the Management Console>
    5. user_created_hours_ago: 24 <or an integer greater than the JWT expiry to delete users that are older than>

    The match_user_subject_pattern will delete any users that were created more than user_created_hours_ago whose subject begins with that pattern. This tutorial hard codes all anonymous users to the ANON realm. Make sure this realm is unique to only those users, so that named users are not accidentally removed.

  12. Test your AWS Lambda function. The test input does not matter and will not be read in this function. If there are no anonymous users older than the user_created_hours_ago, then no users will be deleted.

  13. This function can be triggered in a number of different ways. For example, from Qlik Cloud by adding a Lambda Function URL (as was done for the previous function) and calling it from either a Qlik Application Automation via a Call URL block or a Qlik application via the REST connector. Or, you can tie this AWS Lambda function to a schedule using another AWS service such as EventBridge. With AWS EventBridge, to have it run at the top of every hour, you could set it to a schedule with cron(0,*,?,*,*,*). For more on scheduling with EventBridge, review the AWS Documentation.

Option 2 - qlik-cli

Here is an example script using the qlik-cli with PowerShell:

$match_user_subject_pattern = 'ANON\'
$user_created_hours_ago = '24'
$users = qlik user ls --raw | ConvertFrom-Json
$users_list = $users.data
do {
    $separator = "startingAfter=([^}]*)}"
    $nextURL = ($users.links.next -split $separator)[1]
    $users = ''
    $users = qlik user ls --startingAfter $nextURL --raw | ConvertFrom-Json
    $users_list += $users.data
} while (($users.links.next).Length -gt 0)
foreach($user in $users_list) {
    if ($user.subject -like "$($match_user_subject_pattern)*" -and $user.createdAt -gt (Get-Date).AddHours(-$user_created_hours_ago)) {
        #Write-Host "Deleting user: $user.subject"
        $null = qlik user rm $user.id
    }
}

Appendix— Using a larger keypair

This section is only necessary if you cannot save your base64-encoded private key into the environment variable because it is too large. In a production deployment, it is recommended that you store the private key in a more secure manner, such as in AWS Secrets Manager, fetching it programmatically via AWS Lambda at run time.

The private key can also be stored in base64 inline in the Python code directly if it is too large to be stored as an environment variable. This is not recommended for any production scenario.

  1. Download the sample AWS Lambda Function from here.

  2. Unzip the file.

  3. Open up the lambda_function.py file in your text editor of choice.

  4. Locate the code os.environ['private_key'] and replace it in its entirety with the base64-encoded value of the private key.

  5. The line of code should now resemble:

    private_key = base64.b64decode('LS0tLS1CR...')
    
  6. Save the file.

  7. Within the unzipped folder, select all files, and then zip them back up. Do not zip the parent folder, as this will import into AWS Lambda with the wrong hierarchy. You should be selecting all 11 assets (9 folders and 2 files) and zipping them together as such.

  8. Re-import the zipped function back into AWS Lambda The private_key environment variable can be removed.

Conclusion

Congratulations, you made it to the end of the tutorial. Hopefully, you now have an example application with Qlik content embedded with anonymous access.

Was this page helpful?