Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

Open the “Projects” tab and make sure you’re on the “Cluster IAM” root organization. Then click on “Create New Project” and enter a name for the project (e.g.: “unique”).

...

3.0.1 Project Settings

Once you have created the project, make sure you have these settings checked.

...

3.1 Setup an application

Once you’ve created a project in the root organization an application needs to be setup and configured inside that project. Click on the tile with the “+” and enter an application name (e.g.: “unique-app”). For the type of the application, choose “WEB”.

...

Roles are granted as authorizations to users in Zitadel to give access to certain features of Unique solution.

Panel
panelIconId2139
panelIcon:information_source:
panelIconTextℹ️
bgColor#DEEBFF

All the roles and their descriptions can be found on the following page: Roles and Permissions

...

Navigate to the “Roles” section on the project click on “+ New” and add all roles defined on the “Roles and Permissions” page linked above.

...

  1. In Zitadel, set-up the scope-management service user, following this documentation https://unique-ch.atlassian.net/wiki/spaces/PUB/pages/588546089/Service+User+configuration#Creating-a-service-user. This service user needs no Unique roles to function.

  2. In Zitadel, generate a Personal Access Token (PAT) for the created service user, details to be found here: https://unique-ch.atlassian.net/wiki/spaces/PUB/pages/588546089/Service+User+configuration#Generating-personal-access-token-(PAT). Copy the PAT after creation, you will need it in step 4 to store it in the Azure Key Vault.

  3. In Zitadel, give the scope-management service user, the IAM Owner Viewer role on an instance level. To switch to the instance, simply click on Default Setting at the top right in Zitadel:

    image-20240826-095238.png

    Then add the Service user and give it the role under this button in Zitadel:

    image-20240826-095746.png

    After the role was assigned to the user, it should show up like this in the list of the users with instance roles:

    image-20240826-110925.png
  4. In the Azure Key Vault, search for the keyvault that contains the secret manual-zitadel-scope-mgmt-pat and add the generated PAT from step 2 there as a value.

Info

After setting the PAT in the Key Vault it is necessary to redeploy, so that the scope-managementand the user-sync job can pull the new secret from the Key Vault.

After performing the setup of the scope-management service user, the user-sync cronjob is able to use this service user user’s PAT from the key vault to make requests to the Zitadel API and sync the provisioned users to the Unique backend.

5. Adding Zitadel Actions

tbd - work in progress

6. Configuring SSO

tbd - work in progress

OLD DOCS (will get removed soon - wip)

Scope

This tutorial only applies for clients with a Customer Managed Tenant.

...

Note

If you add any action the function must string match the name of the action else it is not called.

Add one new action called addGrant with this contentZITADEL Actions are custom scripts that you can define to run at specific points during the authentication and authorization processes. After an action was added, it needs to be used by assigning it to one of the available Trigger Types.

Currently the following Actions need to be configured (as required).

Action: addGrant

  1. Create a new addGrant action

    image-20241119-140109.pngImage Added

    Note: add the appropriate roles based on needs

Code Block
function addGrant(ctx, api) {
    api.userGrants.push({
        projectID: '<Resource ID of granted project Unique Apps>',
        projectGrantID: '<Grant ID of granted project Unique Apps>',
        roles: ['chat.chat.basic'] // default roles users get
    });
}

The addGrant actions needs to react on the Trigger Type “Post Creation” because we want to add the default roles for the user after creation.

...

Action: addUserGroupsMetadataEntra

  1. If Entra is used as an IDP, create the action addUserGroupsMetadataEntra

...

Code Block
languagejs
const logger = require("zitadel/log")
const http = require('zitadel/http')

function addUserGroupsMetadataEntra(ctx, api) {
  logger.log('Writing group info 'chat.chat.unlimited', 'chat.chat.upload', 'chat.knowledge.read', 'chat.knowledge.write']
    });
}

This action has to be added as a Post Creation trigger in External Authentication flow.

Add another action called setEmailVerified with this content

Code Block
function setEmailVerifiedon user metadata.');

  const claims = ctx.claimsJSON();
  const parsedGroups = JSON.parse(claims).groups;

  // Enable for debugging
  // logger.log('Claims: ' + JSON.stringify(claims));
  // logger.log('Parsed groups: ' + JSON.stringify(parsedGroups));

  // Get names with additional call to Graph API
  const batchRequests = [];
  for (const groupId of parsedGroups) {
    batchRequests.push({
      "id": groupId,
      "method": "GET",
      "url": `/groups/${groupId}`
    });
  }

  const graphApiBatchResponse = http.fetch('https://graph.microsoft.com/v1.0/$batch', {
    method: 'POST',
    headers: {
      "Authorization": `Bearer ${ctx.accessToken}`,
      "Content-Type": "application/json"
    },
    body: {
      requests: batchRequests
    }
  }).json();

  const entraGroups = [];
  for (const apiResponse of graphApiBatchResponse.responses) {
    const groupId = apiResponse.id;
    const groupName = apiResponse.body.displayName;

    entraGroups.push({
      "id": groupId,
      "displayName": groupName,
    });
  }

  // Check if groups are valid and do not contain empty string values
  if (entraGroups.some(group => group.id === "" || group.displayName === "")) {
    logger.log('Invalid groups found, skipping metadata update.');
    return;
  }

  // append metadata to user
  api.v1.user.appendMetadata("groups", entraGroups);
}

After you’ve added the action, you need to assign it to a trigger.

...

Action: addUserGroupsMetadataOidc

Code:

Code Block
const logger = require("zitadel/log")

function addUserGroupsMetadataOidc(ctx, api) {
  logger.log('Writing group info on user metadata.');

  const claims = ctx.claimsJSON();
  const parsedGroups = JSON.parse(claims).groups;

  // Enable for debugging
  // logger.log('Claims: ' + JSON.stringify(claims));
  // logger.log('Parsed groups: ' + JSON.stringify(parsedGroups));

  // To sync we need need the external groups' ID and display name
  // Verify how the groups are sent on the ID token and adapt to the desired format.
  let groups = parsedGroups.map(group => {
    return {
      "id": group.id,
      "displayName": group.displayName,
    };
  });

  // Check if groups are valid and do not contain empty string values
  if (groups.some(group => group.id === "" || group.displayName === "")) {
    logger.log('Invalid groups found, skipping metadata update.');
    return;
  }

  // append metadata to user
  api.v1.user.appendMetadata("groups", groups);
}

Flow Type: External Authentication
Trigger Type: Post Authentication
Actions: addUserGroupsMetadataOidc

...

Action: addUserGroupsMetadataSaml

Code:

Code Block
const logger = require("zitadel/log")

function addUserGroupsMetadataSaml(ctx, api) {
  logger.log('Writing group info on user metadata.');

  const sentGroups = ctx.v1.providerInfo?.attributes["groups"];

  // Enable for debugging
  // logger.log('Parsed groups: ' + JSON.stringify(sentGroups));

  // To sync we need need the external groups' ID and display name
  // Verify how the groups are sent on the ID token and adapt to the desired format.
  let groups = sentGroups.map(group => {
    return {
      "id": group.id,
      "displayName": group.displayName,
    };
  });

  // Check if groups are valid and do not contain empty string values
  if (groups.some(group => group.id === "" || group.displayName === "")) {
    logger.log('Invalid groups found, skipping metadata update.');
    return;
  }

  // append metadata to user
  api.v1.user.appendMetadata("groups", groups);
}

Flow Type: External Authentication
Trigger Type: Post Authentication
Action: addUserGroupsMetadataSaml

This action needs an additional action:

Action: prefilRegisterFromSAML

Code:

Code Block
function prefilRegisterFromSAML(ctx, api) {
	if (ctx.v1.externalUser?.externalIdpId != "xxxxxxx") {
        return
    }
    let firstname = ctx.v1.providerInfo?.attributes["GivenName"];
    let lastname = ctx.v1.providerInfo?.attributes["Surname"];
    let email = ctx.v1.providerInfo?.attributes["emailaddress"];
    
    
    if (firstname) {
    	api.setFirstName(firstname[0]);
    }
    if (lastname) {
	    api.setLastName(lastname[0]);
    }
    if (email) {
    	api.setEmail(email[0]);
	    api.setEmailVerified(true);
	    api.setPreferredUsername(email[0]);
    }
}

This action has to be added as a Pre Creation trigger in External Authentication flow.

...

 

Flow Type: External Authentication
Trigger Type: Post Authentication
Actions: prefilRegisterFromSAML

Don’t forget to add the IDP ID in the code above. You will find it in the IDP settings when clicking on the IDP and getting the ID from the URL

...

6. Configuring SSO

tbd - work in progress

...