Azure AD + Keycloak — Integration Guide

Parameter Value

Keycloak version

26.0 (Docker)

Azure AD

Microsoft Entra ID

Backend protocol

SAML 2.0 (backend <→ Keycloak)

Broker protocol

OpenID Connect v1.0 (Keycloak <→ Azure AD)

1. Overview

This guide describes the Azure AD login type: how to register an application in Microsoft Entra ID (Azure AD), integrate it with Keycloak as an Identity Broker, and manage users through Keycloak after the integration is complete.

The OneIoT platform uses two separate Keycloak realms — one for the Support Center (SC) portal and one for the Management Console (MC) portal. Both realms are connected to the same Azure AD application.

On the backend this is the saml login type: the backend is a SAML Service Provider and Keycloak is its SAML Identity Provider. Keycloak, in turn, does not store the passwords — it brokers authentication to Azure AD over OIDC. In other words, the Azure AD login type is the SAML setup plus an OIDC Identity-Provider broker to Azure AD configured inside each Keycloak realm.

1.1. How it works

The platform uses a three-layer authentication architecture:

Layer Component Role

Application

OneIoT Backend (Spring Boot)

Initiates the SAML authentication request and receives the authenticated user session.

Broker

Keycloak

Acts as an intermediary between the application and the identity provider. Maps user attributes and adds custom fields required by the application.

Identity Provider

Microsoft Azure AD (Entra ID)

Performs the actual user authentication and identity verification.

1.2. Authentication flow

azure-keycloak-auth-flow

1.3. Attribute flow

Attribute Source Keycloak Mapper Type Description

email

Azure AD token

Attribute Importer

Imported directly from the Azure AD email claim.

username

Azure AD token

Username Template Importer

Derived from ${CLAIM.email} — the user’s email becomes their username.

portal-type

Keycloak (hardcoded)

Hardcoded Attribute

Set per realm: sc for Support Center, mc for Management Console.

user-group

Azure AD token (per-user attribute)

Attribute Importer

The user’s application user-group name, carried per user in an Azure directory-extension claim (see Part 1b: Per-user attributes in Azure (userGroup / domain)). Must exist in the application database.

domain

Azure AD token (per-user attribute)

Attribute Importer

The user’s application domain name, carried per user in an Azure directory-extension claim. Super domain (with a space) grants full access. Must exist in the application database.

Two ways to set user-group / domain — this guide uses per-user

There are two valid strategies for these two attributes:

  • Per-user (this guide) — each Azure user carries their own user-group / domain value, imported into Keycloak by an Attribute Importer mapper. This is what enables multiple roles and multiple domains across users. Requires setting the value on each user in Azure (Part 1b: Per-user attributes in Azure (userGroup / domain)).

  • Single fixed default — every user of the realm gets the same value via a Hardcoded Attribute mapper. Simpler, but all users land in one group and one domain. Use this only when one fixed group/domain per portal is acceptable.

portal-type is always a Hardcoded Attribute (it is per realm, sc / mc), regardless of which strategy you pick for the other two.

1.4. Why Keycloak as a broker (not direct Azure AD integration)?

  • Keycloak adds the portal-type claim (per realm) that Azure AD does not provide, and imports the per-user user-group / domain values from Azure into the fixed claim names the application requires — without the application needing any Azure-specific code.

  • Administrators can manage user attributes, sessions, and access directly in Keycloak without needing Azure AD admin permissions.

  • Each portal (SC / MC) uses a separate Keycloak realm with its own default attribute values, allowing portal-specific configuration.

  • If the identity provider changes in the future (e.g., from Azure AD to Okta or ADFS), only the Keycloak broker configuration needs to be updated — the application remains unchanged.

2. Prerequisites

Before configuring the Azure AD broker:

  1. Deploy Keycloak — see Keycloak Deployment (Docker Compose, keycloak schema, TLS certificate, shared network, HA).

  2. Complete the SAML setup — create the oneiot-sc / oneiot-mc realms, their SAML clients (oneiot-sp-sc / oneiot-sp-mc), attribute mappers, and export the IdP signing certificates, following SAML SSO Setup. The steps below add the Azure AD Identity Provider on top of that SAML configuration.

3. Backend configuration

On the backend, the Azure AD login type is configured exactly like the SAML login type — the backend talks SAML to Keycloak and is unaware of Azure AD. Set app.authentication-type to saml, enable SAML, and point the SAML IdP variables at the Keycloak realms:

SAML_ENABLED=true
SAML_REDIRECT_HOST=https://<PORTALS_HOST>:8843

# IdP = the Keycloak realms (which broker to Azure AD)
SAML_SC_IDP_ENTITY_ID=https://<KEYCLOAK_HOST>:<PORT>/realms/oneiot-sc
SAML_SC_IDP_SSO_URL=https://<KEYCLOAK_HOST>:<PORT>/realms/oneiot-sc/protocol/saml
SAML_MC_IDP_ENTITY_ID=https://<KEYCLOAK_HOST>:<PORT>/realms/oneiot-mc
SAML_MC_IDP_SSO_URL=https://<KEYCLOAK_HOST>:<PORT>/realms/oneiot-mc/protocol/saml

These variables live in the backend per-service env file ui-backend/.env (loaded after the shared .env via env_file: [.env, ./ui-backend/.env], shipped in FT_DISK — ONE_IOT_UI/docker/ui-backend), in its commented SAML block — there is no separate SAML env file. See SAML SSO Setup — Environment variables for the complete variable table and the IdP certificate mount (sc-idp.crt / mc-idp.crt). The backend configuration is identical whether Keycloak authenticates users locally or brokers them to Azure AD — only the Keycloak side differs, as described below.

4. Part 1: Creating an App Registration in Microsoft Entra ID

  1. Open the Microsoft Entra ID portal.

  2. In the left menu, navigate to Manage > App registrations.

  3. Click New registration.

  4. Fill in the following:

    • Name: a descriptive name, for example OneIoT-Keycloak

    • Supported account types: select the option that suits your organization

    • Redirect URI: leave empty for now

  5. Click Register.

After creation, the Overview page is displayed:

Azure App Registration Overview
Field Description

Application (client) ID

Unique identifier of the registered application. Will be used in Keycloak as the Client ID.

Directory (tenant) ID

Azure AD tenant identifier. Part of the Discovery endpoint URL.

Keep this page open — the values above will be needed in the following steps.

5. Part 1b: Per-user attributes in Azure (userGroup / domain)

This part makes each Azure user carry their own user-group and domain, delivered to Keycloak as claims in the Azure ID token. It replaces the single-value Hardcoded Attribute approach for these two attributes and is what enables multiple roles and multiple domains across users.

The value is stored in Azure AD directory-extension attributes on each user and emitted as optional claims. Setting the value per user is done through Microsoft Graph (Graph Explorer or PowerShell) — the Entra portal UI does not expose these fields for editing.

Screenshots

The screenshots in this part were captured on the Keycloak-OneIoT App registration in the Friendly Technologies tenant. The user principal name is shown as <USER_UPN> — substitute the real UPN of the user you are configuring.

5.1. 1b.1 Register two directory-extension attributes on the app

Register the userGroup and domain extension attributes once, on the App registration from Part 1. In Microsoft Graph Explorer, signed in as a directory admin, run:

POST https://graph.microsoft.com/v1.0/applications/{APP_OBJECT_ID}/extensionProperties
Content-Type: application/json

{ "name": "userGroup", "dataType": "String", "targetObjects": ["User"] }

Repeat with "name": "domain". Each response contains a name field of the form extension_{APP_ID_NO_DASHES}_userGroup / extension_{APP_ID_NO_DASHES}_domain — copy both. This full name is the one Microsoft Graph, the app manifest and PowerShell use (Parts 1b.2, 1b.3 and 1c.3). It is not the name the claim carries inside the token — read the warning in 1b.3 Emit the attributes as optional claims (ID token) before configuring the Keycloak mappers in Part 2.6.

{APP_OBJECT_ID} is the App registration’s Object ID (App registration > Overview > Object ID), not the Application (client) ID. {APP_ID_NO_DASHES} in the returned claim name is the Application (client) ID with the dashes removed.

Graph Explorer — extensionProperties registered

5.2. 1b.2 Set the value on each user

For every user that logs in through Azure, set the two attributes to names that exactly match records in the application database for that portal:

PATCH https://graph.microsoft.com/v1.0/users/{USER_ID_OR_UPN}
Content-Type: application/json

{
  "extension_{APP_ID_NO_DASHES}_userGroup": "operator",
  "extension_{APP_ID_NO_DASHES}_domain":    "Super domain"
}
  • userGroup — an existing application user-group name for that portal (e.g. admin, operator).

  • domain — an existing application domain name, or exactly Super domain (with a space) for full access.

The super-domain literal is Super domain — with a space

The backend recognises the super domain only by the exact string Super domain (CommonUtils.SUPER_DOMAIN_NAME). SuperDomain (no space) is not recognised and yields domainId = null. Any other name must already exist in the database, otherwise the user is created with domainId = null / userGroupId = null.

To set the values in bulk, use the Microsoft Graph PowerShell SDK: Update-MgUser -UserId <upn> -AdditionalProperties @{ "extension_…​userGroup" = "operator"; "extension…​_domain" = "Super domain" }.
Graph Explorer — PATCH /users returns 204 No Content

5.3. 1b.3 Emit the attributes as optional claims (ID token)

  1. In the App registration, go to Manage > Token configuration.

  2. Click Add optional claim.

  3. Token type: select ID.

  4. In the list, find and check the two extension attributes (userGroup, domain).

  5. Click Add. If prompted to turn on the necessary Microsoft Graph permissions, accept.

Azure — the two extension attributes added as optional claims on the ID token

After this, the Azure ID token that Keycloak receives carries both per-user values. Keycloak reads them in Part 2.6.

The claim name inside the token is NOT the extension name — read this before Part 2.6

A directory-extension attribute has two different names. They are not interchangeable, and using the wrong one is the single most common reason this setup silently does not work.

  • Microsoft Graph, the app manifest and PowerShell (Parts 1b.2, 1b.3, 1c.3) use the full registered name: extension_{APP_ID_NO_DASHES}_userGroup.

  • Inside the issued JWT — and therefore in the Keycloak mapper Claim field (Part 2.6) — Microsoft emits the same attribute as extn.userGroup. The long extension_…​_userGroup string never appears in the token at all.

Put the long name into the Keycloak Claim field and it matches nothing: the mapper saves without complaint, the Azure login succeeds, but the Attribute Importer writes no value. The user ends up with an empty user-group / domain, and the backend rejects the SAML login with HTTP 500 — Column 'user_group_id' cannot be null.

On top of that, Keycloak reads a dot in the Claim field as a nested-path separator (as in address.locality), so the dot must be escaped with a backslash. The exact strings to type into the two mappers are:

extn\.userGroup
extn\.domain

6. Part 1c: Multi-domain and multi-group access (per-user assignment)

Part 1b is the one-time setup of the mechanism. This part is the recurring operational procedure: giving one Azure user their own application user-group and their own domain, so that different users of the same portal land in different domains with different permission sets.

This is what makes the installation multi-domain: the domain is no longer a single realm-wide constant, it travels with the user in the Azure ID token.

What you set Where it lives What it controls

userGroup

Directory-extension attribute on the Azure user

The application user-group (permission set), matched by name in the OneIoT database.

domain

Directory-extension attribute on the Azure user

The application domain the user works in, matched by name; the literal Super domain grants access to all domains.

6.1. 1c.1 Prerequisites

Before assigning attributes to a user, all of the following must already be true:

  1. The two extension attributes are registered on the App registration — see 1b.1 Register two directory-extension attributes on the app.

  2. Both are emitted as optional claims on the ID token — see 1b.3 Emit the attributes as optional claims (ID token).

  3. Both Keycloak realms have user-group and domain as Attribute Importer mappers with Sync mode override = Force, and the old Hardcoded Attribute mappers for those two names are deleted — see 2.6. Configuring Identity Provider Mappers.

  4. The account performing the change holds the User Administrator or Global Administrator directory role in Entra ID.

  5. Graph Explorer has admin consent for Directory.ReadWrite.All (or User.ReadWrite.All).

  6. The user-group name and the domain name already exist in the OneIoT database for the target portal.

Application Administrator is not enough

The Application Administrator role lets you register the extension attributes (Part 1b.1) and edit Token configuration, but it does not allow writing to user objects. PATCH /users/…​ then fails with:

{ "error": { "code": "Authorization_RequestDenied",
             "message": "Insufficient privileges to complete the operation." } }

Granting User.ReadWrite.All consent does not fix this — a delegated permission never exceeds the rights of the signed-in user. You need the User Administrator role (or Global Administrator) on the account itself.

To check which roles the signed-in account holds:

GET https://graph.microsoft.com/v1.0/me/transitiveMemberOf/microsoft.graph.directoryRole?$select=displayName

6.2. 1c.2 Choosing the values

userGroup

An existing application user-group name for that portal, for example admin or operator. The name is matched case-sensitively against the database.

domain

Either an existing application domain name, or exactly Super domain for access to all domains.

The super-domain literal is Super domain — with a space

The backend recognises the super domain by the exact string Super domain (CommonUtils.SUPER_DOMAIN_NAME), compared case-insensitively. SuperDomain (no space) is not recognised and yields domainId = null. Any other value must already exist in the database, otherwise the user is created with domainId = null / userGroupId = null and is effectively locked out of the portal.

A typical multi-domain assignment looks like this:

User Portal userGroup domain

Platform administrator

SC

admin

Super domain

Operator, one customer

SC

operator

AussieBB

Operator, another customer

SC

operator

Acme Telecom

Read-only auditor

MC

viewer

Acme Telecom

6.3. 1c.3 Setting the values on a user

The Entra portal UI does not expose directory-extension attributes for editing — they can only be written through Microsoft Graph.

  1. Open Microsoft Graph Explorer and sign in with the account that holds the User Administrator role.

  2. Set the HTTP method to PATCH and the version to v1.0.

  3. Enter the request URL, where <USER_UPN> is the user’s UPN (or their object ID):

    https://graph.microsoft.com/v1.0/users/<USER_UPN>
  4. Open the Request body tab and paste the payload, substituting the exact claim names returned in Part 1b.1 and the values chosen in 1c.2:

    {
      "extension_{APP_ID_NO_DASHES}_userGroup": "admin",
      "extension_{APP_ID_NO_DASHES}_domain":    "Super domain"
    }
  5. Click Run query.

A response of 204 No Content means the attributes were written.

Graph Explorer — PATCH /users returns 204 No Content

Both attributes can be set independently. Sending only one key in the payload leaves the other one untouched.

6.4. 1c.4 Verifying the values in Azure

Directory-extension attributes are not returned by a plain GET /users/{id} — they must be requested explicitly with $select:

GET https://graph.microsoft.com/v1.0/users/<USER_UPN>?$select=userPrincipalName,extension_{APP_ID_NO_DASHES}_userGroup,extension_{APP_ID_NO_DASHES}_domain

The response must contain both values:

Graph Explorer — both extension attributes read back from the user

6.5. 1c.5 Verifying end to end in Keycloak

  1. Sign the user out completely — both from the portal/Keycloak session and from Microsoft (https://login.microsoftonline.com/logout). A cached session keeps serving the previous token and the new values will not appear.

  2. Log in to the portal through Login with Microsoft.

  3. In the Keycloak admin console, open the realm for that portal (oneiot-sc / oneiot-mc) and go to Users > the user > Attributes.

  4. Confirm the attributes:

    user-group

    the value set in 1c.3

    domain

    the value set in 1c.3

    portal-type

    sc or mc, from the hardcoded mapper

  5. Confirm in the portal that the user sees exactly the data of their domain and the permissions of their group.

Because the mappers use Sync mode override = Force, the values are refreshed on every login, not only when the Keycloak user is first created. Any values left over from the old Hardcoded Attribute mappers are overwritten on the next login.

6.6. 1c.6 Changing or revoking access later

To move a user to another domain or group, repeat 1c.3 with the new values. The change takes effect at the user’s next login.

To clear an attribute, send null:

{ "extension_{APP_ID_NO_DASHES}_domain": null }

Keycloak keeps the previously imported attribute on the user until the next successful broker login. When revoking access, also terminate the user’s active sessions (Users > the user > Sessions, or Sessions at realm level), otherwise the current session continues with the old domain until it expires.

6.7. 1c.7 Bulk assignment

For more than a handful of users, use the Microsoft Graph PowerShell SDK instead of Graph Explorer:

Connect-MgGraph -Scopes "User.ReadWrite.All"

Update-MgUser -UserId "<USER_UPN>" -AdditionalProperties @{
  "extension_{APP_ID_NO_DASHES}_userGroup" = "operator"
  "extension_{APP_ID_NO_DASHES}_domain"    = "AussieBB"
}

Driving it from a CSV with UserPrincipalName,UserGroup,Domain columns:

Import-Csv .\users.csv | ForEach-Object {
  Update-MgUser -UserId $_.UserPrincipalName -AdditionalProperties @{
    "extension_{APP_ID_NO_DASHES}_userGroup" = $_.UserGroup
    "extension_{APP_ID_NO_DASHES}_domain"    = $_.Domain
  }
}

6.8. 1c.8 Troubleshooting

Symptom Cause Fix

PATCH returns 403 Authorization_RequestDenied

The signed-in account lacks the User Administrator role, or Graph Explorer has no admin consent for Directory.ReadWrite.All / User.ReadWrite.All.

Check the roles with the GET /me/transitiveMemberOf/…​ query in 1c.1, and grant consent on the Modify permissions tab. The role is the more common cause — consent alone does not help.

PATCH returns 400 Unable to read JSON request payload

Malformed body — typically a stray quote left in the editor when the previous payload was overwritten.

Clear the Request body field completely and retype the JSON.

PATCH returns 404

Wrong UPN, or the guest user’s UPN differs from their email address.

Use the user’s object ID instead of the UPN.

The attribute is missing from the GET response

Directory-extension attributes are only returned when requested.

Add them to $select as shown in 1c.4.

user-group / domain are empty in Keycloak after login

Almost always the mapper Claim field: it still holds the long extension_…​_userGroup name (which never appears in the token), or the dot is not escaped. Less often, the optional claim was never added in Token configuration.

Set the mapper Claim to exactly extn\.userGroup / extn\.domain, backslash included, then re-check Token configuration (1b.3). See the warning in 1b.3 Emit the attributes as optional claims (ID token).

The value in Keycloak does not change after being updated in Azure

The mapper Sync mode is not Force, or a leftover Hardcoded Attribute mapper with the same name is overwriting the imported value.

Set Sync mode override = Force and delete the hardcoded user-group / domain mappers (2.6. Configuring Identity Provider Mappers).

The user logs in but the portal returns NO_MATCH_AUTH / 403, or the domain is null

The imported name does not exist in the OneIoT database for that portal, or Super domain was written without the space.

Correct the value in Azure to a name that exists in the database and have the user log in again.

7. Part 2: Integrating Azure AD into Keycloak

For initial Keycloak setup and realm creation, see SAML SSO — Integration Guide.

The steps below must be performed for each realm (oneiot-sc and oneiot-mc).

7.1. 2.1. Creating the Identity Provider

  1. Open the Keycloak Admin Console at https://<KEYCLOAK_HOST>:8500.

  2. Select the target realm (e.g., oneiot-sc).

  3. In the left menu, navigate to Identity providers.

  4. Click Add provider and select OpenID Connect v1.0.

  5. In the Alias field, change the default value from oidc to microsoft.

    Keycloak — Identity Provider General Settings
    Changing the Alias is mandatory. The Alias determines the broker endpoint URL path.
  6. The Redirect URI field at the top is auto-generated based on the Alias. Copy this value — it will be added to Azure in the next step.

7.2. 2.2. Adding the Redirect URI in Azure

  1. Go back to the Azure App Registration Overview page.

  2. Click Add a Redirect URI.

  3. Click Add a platform and select Web.

  4. Paste the Redirect URI copied from Keycloak.

  5. Click Save.

Repeat steps 2.1—​2.2 for the second realm (e.g., oneiot-mc), so that Azure has two Redirect URIs:

  • https://rd.friendly-tech.com:8500/realms/oneiot-sc/broker/microsoft/endpoint

  • https://rd.friendly-tech.com:8500/realms/oneiot-mc/broker/microsoft/endpoint

7.3. 2.3. Creating a Client Secret in Azure

A Client Secret is required for Keycloak to authenticate with Azure AD. One secret is shared across both Keycloak realms.

  1. In the Azure App Registration, navigate to Manage > Certificates & secrets.

  2. Go to the Client secrets tab.

  3. Click + New client secret.

    Azure — Creating a Client Secret
  4. Enter a description and choose an expiration period.

  5. Click Add.

Copy the secret Value immediately after creation. It is only displayed once. If you lose it, you will need to create a new one.

7.4. 2.4. Obtaining the Discovery Endpoint from Azure

  1. Go back to the Azure App Registration Overview page.

  2. Click the Endpoints button in the top toolbar.

  3. Copy the OpenID Connect metadata document URL.

    The URL format is:

    https://login.microsoftonline.com/<TENANT_ID>/v2.0/.well-known/openid-configuration

7.5. 2.5. Completing the Identity Provider Configuration in Keycloak

Go back to the Keycloak Identity Provider configuration page and fill in the remaining fields:

  1. Discovery endpoint — paste the OpenID Connect metadata document URL from Azure.

    Keycloak — Discovery Endpoint
  2. Client ID — enter the Application (client) ID from the Azure App Registration Overview.

  3. Client Secret — enter the secret Value created in Azure.

    Keycloak — Client Secret
  4. Click Save.

Repeat this step for the second realm using the same Discovery endpoint, Client ID, and Client Secret.

After completing both realms, each Keycloak realm should have a configured microsoft Identity Provider linked to the same Azure AD application.

7.6. 2.6. Configuring Identity Provider Mappers

Mappers define how user attributes from Azure AD are imported into Keycloak. This configuration must be done in both realms on each Identity Provider.

  1. Open the Identity Provider (Login with Microsoft).

  2. Go to the Mappers tab.

  3. Click Add mapper and create the mappers described below.

After all mappers are created, the list should look like this:

Keycloak — Identity Provider Mappers

7.6.1. email

Imports the user email from Azure AD.

Mapper — email

Name

email

Sync mode override

Force

Mapper type

Attribute Importer

Claim

email

User Attribute Name

email

7.6.2. username

Sets the Keycloak username based on the email claim from Azure AD.

Mapper — username

Name

username

Sync mode override

Inherit

Mapper type

Username Template Importer

Template

${CLAIM.email}

Target

LOCAL

7.6.3. portal-type

A hardcoded attribute that defines which portal the user belongs to. This value is not provided by Azure AD — it is assigned by default for every user logging in through this realm.

Mapper — portal-type

Name

portal-type

Sync mode override

Import

Mapper type

Hardcoded Attribute

User Attribute

portal-type

User Attribute Value

sc (for Support Center realm) / mc (for Management Console realm)

In the oneiot-sc realm set the value to sc. In the oneiot-mc realm set the value to mc.
Use the token claim names extn\.userGroup / extn\.domain — with the backslash

The two Attribute Importers below must be given the token claim name, not the long extension_{APP_ID_NO_DASHES}_…​ name from Part 1b.1, and the dot must be escaped with a backslash. Getting this wrong fails silently: the mapper saves, the login succeeds, and the user simply has no user-group / domain. Full explanation in 1b.3 Emit the attributes as optional claims (ID token).

7.6.4. user-group

Imports the per-user application user-group from the Azure extension claim registered in Part 1b: Per-user attributes in Azure (userGroup / domain). Sync mode override = Force so the value is refreshed on every login (not only at user creation).

Name

user-group

Sync mode override

Force

Mapper type

Attribute Importer

Claim

extn\.userGroup (token claim name, backslash-escaped — not the long extension_…​_userGroup name)

User Attribute Name

user-group

Keycloak — user-group Attribute Importer (Claim

7.6.5. domain

Imports the per-user application domain from the Azure extension claim. Identical to the user-group mapper, with domain substituted.

Name

domain

Sync mode override

Force

Mapper type

Attribute Importer

Claim

extn\.domain (token claim name, backslash-escaped — not the long extension_…​_domain name)

User Attribute Name

domain

Keycloak — domain Attribute Importer (Claim
Remove the old Hardcoded Attribute mappers for user-group / domain first

Earlier revisions of this guide configured user-group and domain as Hardcoded Attribute mappers (one fixed value for every user). If those mappers still exist on the Identity Provider, delete them before adding the Attribute Importers above — a leftover hardcoded mapper writes the same value for every user and overrides the per-user import. Keep only the portal-type hardcoded mapper.

portal-type stays a Hardcoded Attribute (it is per realm, sc / mc). Only user-group and domain become Attribute Importer mappers bound to the Azure per-user extension claims. The imported values must exactly match a user-group / domain that already exists in the application database for that portal, or the user is created with userGroupId = null / domainId = null.

8. Part 3: Configuring Authentication Flows

All authentication flow settings described below must be configured in each realm separately.

8.1. 3.1. Browser Flow — Auto-redirect to Azure AD

By default, Keycloak shows its own login page. To skip it and redirect users directly to the Azure AD login page, create a custom Browser flow.

  1. In the left menu, navigate to Authentication.

  2. Find the browser flow in the list.

  3. Click the three-dot menu on the right and select Duplicate.

    Keycloak — Duplicate browser flow
  4. Enter a name for the new flow (e.g., browser-with-auto-idp) and click Duplicate.

  5. Open the new flow.

    Keycloak — Browser flow details
  6. Click the gear icon on the Identity Provider Redirector step.

  7. In the configuration dialog, set Default Identity Provider to microsoft (the same Alias used when creating the Identity Provider).

    Keycloak — Identity Provider Redirector config
  8. Click Save.

  9. Go back to the Authentication flows list.

  10. Find the browser-with-auto-idp flow, click the three-dot menu and select Bind flow.

    Keycloak — Bind flow
  11. Select Browser flow and confirm.

After binding, the Authentication page should show browser-with-auto-idp as the active Browser flow:

Keycloak — Authentication flows final state

8.2. 3.2. Identity Provider — Prompt Setting

To configure how Azure AD prompts users during login:

  1. Open the Identity Provider (microsoft) in the current realm.

  2. Scroll down and expand the Advanced section.

  3. Find the Prompt dropdown and select the preferred option.

    Keycloak — Prompt setting
    Option Behavior

    Select account

    Azure AD shows a list of logged-in Microsoft accounts and allows choosing another one. Recommended.

    Login

    Always shows the Azure AD login form, even if the user is already signed in.

    Consent

    Asks the user for consent to share data with the application.

    None

    No prompt — silently authenticates if a session exists.

Select account is recommended because it allows users to choose between multiple Microsoft accounts or log in with a different one.

By default, when a user logs in via Azure AD for the first time, Keycloak asks to review the profile and verify the email. Since Keycloak acts only as a broker and we trust Azure AD to verify users, these steps should be disabled.

  1. In the Authentication flows list, find the first broker login flow.

    Keycloak — Duplicate first broker login
  2. Click the three-dot menu and select Duplicate.

  3. Enter a name (e.g., first broker login - auto-link) and click Duplicate.

    Keycloak — Duplicate dialog
  4. Open the new flow and change the following steps to Disabled:

    Keycloak — First broker login auto-link settings
    Step Set to Reason

    Review Profile

    Disabled

    Users should not be asked to fill in profile fields on first login.

    Confirm link existing account

    Disabled

    Skip the confirmation prompt when linking to an existing account.

    Verify existing account by Email

    Disabled

    Skip email verification — Azure AD already verified the user.

  5. Save the flow.

8.4. 3.4. Assigning the First Login Flow to the Identity Provider

  1. Open the Identity Provider (microsoft) settings.

  2. Scroll down to the First login flow override field.

  3. Select the newly created flow (first broker login - auto-link).

    Keycloak — First login flow override
  4. Click Save.

Both authentication flows (Browser flow and First login flow) must be configured in each realm separately. Repeat all steps from 3.1 to 3.4 for the second realm.

9. Part 4: Managing Users in Keycloak

9.1. User Attributes

Users created through the Azure AD broker receive:

  • portal-type — sc or mc depending on the realm (Hardcoded Attribute mapper).

  • user-group — imported per user from the Azure userGroup extension claim (Attribute Importer).

  • domain — imported per user from the Azure domain extension claim (Attribute Importer).

Change per-user values in Azure, not in Keycloak

Because the user-group / domain mappers use Sync mode = Force, Keycloak re-imports the Azure value on every login and overwrites any manual edit made on the Keycloak user. To change a user’s group or domain permanently, update the corresponding extension attribute on the user in Azure (Part 1b.2). Editing it only in Keycloak lasts until the next login.

9.2. Editing User Attributes

  1. In the left menu, navigate to Users.

  2. Select the user.

  3. Go to the Attributes tab.

  4. Update the attribute values as needed and click Save.

For user-group and domain this is only a temporary override — the next login re-imports the Azure value (Sync mode = Force). Change these permanently in Azure (Part 1b.2). Use the Keycloak Attributes tab for a one-off, until-next-login adjustment.

9.3. Terminating User Sessions

  1. In the left menu, navigate to Users.

  2. Select the user.

  3. Go to the Sessions tab.

  4. Click Sign out to terminate the active session.

9.4. Deleting Users

  1. In the left menu, navigate to Users.

  2. Select the user.

  3. Click Delete and confirm.

Each realm manages its users independently. Changes made in one realm do not affect the other.

9.5. Recommendations

  • Set every Azure user’s userGroup / domain extension attribute (Part 1b.2) to names that already exist in the application database for that portal. A user with an empty or unknown user-group is blocked; an unknown domain yields domainId = null.

  • Super domain (with a space) grants access to every domain. Assign it deliberately — most users should carry a specific domain instead.

  • To restrict a user, change their userGroup extension attribute in Azure to a group with limited permissions and have them re-login.

  • Every user must belong to exactly one group and one domain value; the claims are single-valued.