Manage users & groups with OAuth impersonation
Introduction
When embedding Qlik Cloud into your own application, controlling user identity and access from your portal is key. OAuth2 impersonation offers a modern, cookie-less alternative to JWT for scenarios where your portal acts as the identity provider (IDP) and wants to control user access at the moment of embedding - without requiring authentication directly through a Qlik Cloud-configured identity provider.
Access control in Qlik Cloud is based on multiple factors, and it’s your choice how to configure this for your tenant. At the very least, you can expect to manage roles on your users and/or groups to control access to content and features.
This guide demonstrates how to manage these on-the-fly in an embedded scenario.
If you’re just starting your embedding journey with qlik-embed, review the authentication overview to understand the available authentication options.
Why use OAuth2 impersonation?
OAuth2 impersonation, uses Qlik’s OAuth2 flow to establish the user context. It allows your portal to:
- Provide user attributes dynamically at request time.
- Avoid cookies or interactive login flows.
- Support auditing and event tracking, since actions are performed by real users (via impersonation).
If you want to take a look at a working implementation, review the OAuth impersonation embedded example tutorial.
Group-based access control is essential
When using impersonation, you typically do not want to assign content access or roles on a per-user basis. Instead, you should rely on groups, which your portal can configure during the token request process.
This ensures your embedded experience:
- Scales with your user base.
- Is easier to audit and trace user access in the tenant.
- Keeps tenant configuration maintainable.
Example scenario
Let’s say you run a customer portal that embeds analytics for your users. Your requirements during the user login flow are:
- Must return an impersonated token which the signed in user can use to access the embedded analytics.
- If the user is new, create the user in the Qlik Cloud tenant, so that any content they create will persist to their next session (for example, data alerts, subscriptions, bookmarks, self service content).
- If the user exists, re-use the existing account so they keep their personal data.
- The user’s identifier is their email address. This is what is used by the portal to uniquely identify them.
- If the user isn’t assigned to the
Consumer
custom group, add this assignment.
The tenant already has:
- A managed space with the
Consumer
group added withCan view
permissions. This makes content such as Qlik Sense apps in the space available to members of the group. - Assigned roles to the
Consumer
group which give permission to use the features you wish them to use in the tenant.
Your web app:
- Has a login form and flow.
- Is running a service such as node.js, which allows you to run server-side code.
- Your implementation is compatible with the good practices for OAuth2 impersonation.
Example code
What this example does
The following code demonstrates a complete OAuth2 impersonation flow that:
- User Discovery: Searches for existing users in the Qlik Cloud tenant using their email address.
- User Creation: Automatically creates new users if they don’t exist in the tenant.
- Group Management: Ensures users are assigned to the specified custom group
(for example,
Consumer
). - Token Generation: Returns an impersonated access token that can be used for embedding.
- Error Handling: Provides comprehensive error handling and validation.
- Input Validation: Validates required parameters and email format.
Integration with your web application
In a typical embedded scenario:
- Backend Integration: The
getImpersonatedUserToken()
function runs on your web application’s backend (for example, Node.js server). - User Session: Your web app provides the user information (email, name, subject) from the logged-in user’s session.
- Frontend Usage: The output from the
exampleUsage()
function would be used in the qlik-embed tags in the frontend.
Code
// Import necessary modules from @qlik/apiimport {auth,users,} from "@qlik/api";
// Specify admin authentication configuration with relevant admin scopes. This will be// used for actions that require admin privileges, such as user management.const authAdmin = { authType: "oauth2", host: "https://mytenant.us.qlikcloud.com", clientId: process.env.QLIK_ADMIN_CLIENT_ID, clientSecret: process.env.QLIK_ADMIN_CLIENT_SECRET, scope: "admin.users",};
// Specify user authentication configuration with relevant user scopes. This will be// used for generating the user access token.const authUser = { authType: "oauth2", host: "https://mytenant.us.qlikcloud.com", clientId: process.env.QLIK_USER_CLIENT_ID, clientSecret: process.env.QLIK_USER_CLIENT_SECRET, scope: "user_default",};
/** * Adds a custom group to a user's assigned groups if they don't already have it * @param {string} userId - The ID of the user * @param {string} groupName - The name of the group to add * @param {Array} currentGroups - The user's current assigned groups * @returns {Promise<void>} */async function ensureUserHasGroup(userId, groupName, currentGroups = []) {// Check if user already has the specified group with providerType "custom"const hasRequiredGroup = currentGroups.some(group => group.name === groupName && group.providerType === "custom");
if (!hasRequiredGroup) { console.log(`User ${userId} does not have ${groupName} group, adding it`);
// Add the specified group const updatedGroups = [ ...currentGroups, { name: groupName, providerType: "custom" } ];
// Update the user with the new groups await users.patchUser( userId, [ { op: "replace", path: "/assignedGroups", value: updatedGroups } ], { hostConfig: { ...authAdmin, }, } );
console.log(`Added ${groupName} group to user ${userId}`);} else { console.log(`User ${userId} already has ${groupName} group`);}}
/** * Gets an impersonated access token for a user based on their email and group name * @param {string} userEmail - The email address of the user * @param {string} userName - The name of the user (used when creating new users) * @param {string} userSubject - The subject of the user (used when creating new users, must be unique) * @param {string} groupName - The name of the group to assign to the user * @returns {Promise<string>} - The impersonated access token */async function getImpersonatedUserToken(userEmail, userName, userSubject, groupName) {// Input validationif (!userEmail || !userName || !userSubject || !groupName) { throw new Error("All parameters are required: userEmail, userName, userSubject, groupName");}
if (!userEmail.includes("@")) { throw new Error("Invalid email format provided");}
try { // Step 1: Get user ID using their email const { data: foundUsers } = await users.getUsers( { filter: `email eq "${userEmail}" and status eq "active"`, }, { hostConfig: { ...authAdmin, }, } );
let userId;
if (!foundUsers || foundUsers.length === 0) { // User doesn't exist, create them console.log(`User with email ${userEmail} not found, creating new user`);
const userPayload = { name: userName, email: userEmail, subject: userSubject, };
const newUser = await users.createUser(userPayload, { hostConfig: { ...authAdmin, }, });
userId = newUser.data.id; console.log(`Created new user with ID: ${userId} for email: ${userEmail}`);
// Add the required group to the new user await ensureUserHasGroup(userId, groupName, []); } else if (foundUsers.length === 1) { userId = foundUsers[0].id; const existingUser = foundUsers[0]; console.log(`Found existing user ID: ${userId} for email: ${userEmail}`);
// Ensure the existing user has the required group await ensureUserHasGroup(userId, groupName, existingUser.assignedGroups || []); } else { // Multiple users found - this should never happen and is an error throw new Error(`Multiple users found with email ${userEmail}. This is not possible in Qlik Cloud since user emails must be unique.`); }
// Step 2: Generate impersonated access token for the user const accessToken = await auth.getAccessToken({ hostConfig: { ...authUser, userId: userId, scope: "user_default", }, });
console.log(`Generated impersonated token for user: ${userId}`); return accessToken;
} catch (error) { console.error("Error generating impersonated user token:", error); throw new Error(`Failed to get impersonated token for user ${userEmail}: ${error.message}`);}}
// Example usage:// This function demonstrates how to get an impersonated access token for a userasync function exampleUsage() { try { const accessToken = await getImpersonatedUserToken( "john.doe@qlik.com", // userEmail - the email of the user to impersonate "John Doe", // userName - display name for the user "6893db654802ab50d228526e", // userSubject - subject identifier (avoid using personal identifiers such as email) "Consumer" // groupName - the custom group to assign );
console.log("Successfully obtained access token:", accessToken); return accessToken; } catch (error) { console.error("Failed to get impersonated token:", error.message); throw error; }}
// Uncomment the line below to run the example// exampleUsage();