Identity
The Union.ai identity service provides user and application management for self-hosted deployments. It powers the User Management page in the console, enabling administrators to view, invite, and manage users and service accounts.
The identity service integrates with your identity provider to fetch user and application data. Without it, the User Management page displays a limited view based only on locally-cached authorization data.
Prerequisites
Before configuring the identity service:
- Authentication is configured and working — see the authentication guide
- Authorization mode is Noop or Union — the identity service integrates with Union.ai’s built-in authorization. If you use External authorization mode, the identity service is not applicable — your external authorization system handles user and application management instead.
Overview
The identity service acts as a bridge between your identity provider and the Union.ai console:
| Feature | Without identity service | With identity service |
|---|---|---|
| View users | Only users who have logged in | All users from your IdP directory |
| View applications | Only bootstrapped service accounts | All registered OAuth applications |
| Invite users | Not available | Invite by email (creates authorization record) |
| User search | Limited to cached data | Search your IdP directory |
Identity providers
The identity service supports multiple backend providers:
| Provider | Value | Description |
|---|---|---|
| Noop | noop |
Identity service disabled. User Management shows only locally-cached authorization data. This is the default. |
| Azure | azure |
Microsoft Entra ID (Azure AD). Fetches users and applications via Microsoft Graph API. |
Additional providers (Okta, Google Workspace) may be added in future releases. If you need a provider not listed here, contact Union.ai support.
Configuring the Azure provider (Entra ID)
The Azure provider uses the Microsoft Graph API to fetch users and applications from your Entra ID tenant.
Step 1: Create an Entra ID app registration for Graph API
Create a separate app registration in Entra ID for the identity service. This app is distinct from the five OAuth apps used for authentication — it only needs Graph API permissions, not OIDC flows.
- In the Azure portal, go to Microsoft Entra ID → App registrations → New registration
- Name:
<your-org>-identity-service(or similar) - Supported account types: Single tenant (this directory only)
- Redirect URI: Leave blank (not needed for client_credentials)
- Click Register
Step 2: Configure API permissions
The identity service needs Application permissions (not Delegated) to manage users, groups, and applications:
- Go to API permissions → Add a permission → Microsoft Graph → Application permissions
- Add the following permissions:
User.Read.All— read all user profiles for the User Management pageGroupMember.ReadWrite.All— list and manage group members (required whengroupIdis configured)Application.ReadWrite.All— create, update, and delete OAuth application registrations
- Click Grant admin consent for <your-tenant>
These are high-privilege permissions that include write access. The identity service uses these permissions to:
- Read user profiles for display in User Management
- Manage group memberships when group-based access is configured
- Create and manage OAuth application registrations for new service accounts
Review your organization’s security policies before granting admin consent. If your organization cannot grant write permissions, the identity service will have reduced functionality.
Step 3: Create a client secret
- Go to Certificates & secrets → Client secrets → New client secret
- Description:
identity-service - Expires: Choose an appropriate expiration (e.g., 24 months)
- Click Add and copy the secret value immediately — you cannot retrieve it later
Store the secret in your secret management system (AWS Secrets Manager, GCP Secret Manager, HashiCorp Vault, etc.).
Step 4: Note the required values
You’ll need these values for Helm configuration. All values are found on the identity service app registration’s Overview page in the Azure portal (Microsoft Entra ID → App registrations → <your-org>-identity-service):
| Value | Azure portal location |
|---|---|
| Tenant ID | Directory (tenant) ID field on the Overview page |
| Client ID | Application (client) ID field on the Overview page |
| Client Secret | The secret value you copied in Step 3 (not visible after creation) |
Control plane Helm configuration
Configure the identity service in your controlplane Helm values:
services:
identity:
configMap:
identity:
app:
identityProviderConfig:
provider: "azure"
azure:
tenantId: "<your-entra-tenant-id>"
clientId: "<identity-service-app-client-id>"
clientSecretName: "azure_client_secret"
groupId: "" # Optional: limit to users in a specific Entra groupConfiguration reference
| Field | Description |
|---|---|
provider |
Identity provider backend. Set to "azure" for Entra ID, or "noop" to disable. |
azure.tenantId |
Your Microsoft Entra ID tenant ID (directory ID). |
azure.clientId |
The Application (client) ID of the identity service app registration. |
azure.clientSecretName |
The key name in the Kubernetes secret where the client secret is stored. The secret file is mounted at /etc/secrets/union/. |
azure.groupId |
(Optional) Limit User Management to members of a specific Entra group. Leave empty to show all users in the tenant. |
Secret delivery
The identity service reads the Graph API client secret from a file mounted at /etc/secrets/union/<clientSecretName>. Deliver this secret using External Secrets Operator or your preferred secret management approach.
External Secrets Operator (recommended)
Create an ExternalSecret that syncs the Graph API client secret into the controlplane service secret:
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: identity-azure-secret
namespace: union-cp
spec:
secretStoreRef:
name: default
kind: SecretStore
refreshInterval: 1h
target:
name: service-shared-secret # The shared controlplane secret
creationPolicy: Merge
deletionPolicy: Retain
data:
- secretKey: azure_client_secret
remoteRef:
key: "<your-secret-store-path>" # e.g., "staging/entraid/identity-service-client-secret"creationPolicy: Merge adds the azure_client_secret key to the existing service-shared-secret Kubernetes secret without overwriting other keys.
Direct Kubernetes secret
If you manage secrets directly:
kubectl patch secret service-shared-secret -n union-cp \
--type='json' \
-p='[{"op": "add", "path": "/data/azure_client_secret", "value": "'$(echo -n '<YOUR_SECRET>' | base64)'"}]'Verifying the configuration
After deploying with the identity service configured:
-
Check the identity pod is running:
kubectl get pods -n union-cp -l app.kubernetes.io/name=identity -
Check for configuration errors in logs:
kubectl logs -n union-cp deploy/identity --tail=50 | grep -i "error\|provider\|azure" -
Verify User Management in the console:
- Navigate to Settings → Users in the Union.ai console
- You should see users from your Entra ID tenant (not just users who have logged in)
- The user count should match your Entra directory (or group, if
groupIdis configured)
Troubleshooting
User Management page shows “Unable to load users”
-
Check identity pod logs for Graph API errors:
kubectl logs -n union-cp deploy/identity --tail=100 | grep -i "graph\|error\|unauthorized" -
Verify the client secret is correctly mounted:
kubectl exec -n union-cp deploy/identity -- cat /etc/secrets/union/azure_client_secret -
Verify API permissions — ensure
User.Read.AllandApplication.Read.Allhave admin consent in the Azure portal.
Users appear but some are missing
-
Check
groupIdconfiguration — if set, only members of that group appear. -
Check Entra ID user status — disabled or deleted users may be filtered out.
-
Check pagination — for large directories (>1000 users), verify the identity service handles Graph API pagination correctly by checking logs for pagination-related errors.
Identity pod fails to start
-
Check secret mount:
kubectl describe pod -n union-cp -l app.kubernetes.io/name=identity | grep -A5 "Mounts:" -
Check ExternalSecret sync status:
kubectl get externalsecret -n union-cpAll ExternalSecrets should show
SecretSyncedstatus.
“Unauthorized” errors in identity logs
The Graph API client secret may be expired or invalid:
- Check secret expiration in the Azure portal under the app registration’s Certificates & secrets.
- Rotate the secret and update your secret store.
- Restart the identity pod to pick up the new secret:
kubectl rollout restart deployment/identity -n union-cp