Skip to main content

Microsoft Entra ID SSO for AlmaForge

AlmaForge authenticates users against Microsoft Entra ID (formerly Azure Active Directory) through the standard OIDC flow. Map Entra security groups to AlmaForge roles. Membership changes take effect on the user's next sign-in. Manage group membership in Entra and revoke active access in AlmaForge when the change must take effect immediately.

The integration is built into almad. Configure it with an OIDCConnector resource whose spec.provider is set to microsoft. See the generic OIDC guide for the full field list and the shared verification, high-availability, and break-glass content. This page covers only the Entra-specific clicks.

The OIDC integration supports group-based role mapping, tenant isolation, and reauthentication.

TL;DR​

  1. Register a new app in Entra ID with the OIDC callback URL (Step 1).
  2. Create a client secret (Step 2).
  3. Add the groups claim to the ID token (Step 3).
  4. Apply the OIDCConnector from Step 4.
  5. Verify with a real login (Step 5).
Realistic time estimate

The AlmaForge side of this integration takes 2 to 3 minutes. You write a single OIDCConnector resource and apply it.

The Entra ID side is the slow part. Expect to spend up to half an hour clicking through the Entra admin center, finding the right blades, and collecting the tenant ID, the client ID, the secret, and the object ID of each group you want to map. Start the Entra work first and save the alma apply for the end.

Limitations​

Test recovery before changing SSO. AlmaForge has no local accounts. If this is the only connector, a failure prevents new logins. Review SSO-first behavior on the OIDC page, and confirm your break-glass procedure works before running apply on a production cluster.

Client secrets expire. Entra ID caps client secrets at 24 months and does not let you create a non-expiring secret. When the secret expires, token exchange fails for every user. See Client secret rotation under Operations for the rotation procedure and the observable symptom.

Group overage is not followed. Entra does not emit the groups claim inline for users who are members of more than approximately 200 security groups. Instead, it emits a _claim_names hint that points back to Microsoft Graph. AlmaForge does not currently follow the overage pointer. If users hit the limit, configure Entra to emit only groups assigned to this application, then assign and map the required groups. Users must be direct members of those assigned groups, because this option excludes nested membership. Reducing the connector's mapping does not reduce the groups Entra puts in the token. See Microsoft's group-claim guidance.

Match groups by object ID, never by display name. AlmaForge matches claimsToRoles against the groups claim exactly as the token carries it, and resolves nothing. The directory assigns object IDs. Display names are chosen by whoever creates the group, Entra does not require them to be unique, and creating a group is a default permission for an ordinary tenant user, so a name is not something a login decision can rest on.

Which of the two lands in the token is a tenant setting. Token configuration → Add groups claim offers Group ID as well as Cloud-only group display names and sAMAccountName (Step 3 selects Group ID). Pick Group ID, and copy each group's Object ID from its Overview page in Entra. If your tenant emits names instead, claimsToRoles will match on a value your users can choose.

Keep credentials outside the manifest. clientSecret can load its value from a file or environment variable. See Secret storage.

Prerequisites​

Before you start, make sure you have:

  • A running AlmaForge cluster with an Auth Service that you can reach over the network and a working alma CLI pointed at it.
  • An Entra ID tenant and an account with permission to create App registrations.
  • One or more Entra security groups that you will map to AlmaForge roles, and the Object ID of each. A fresh group dedicated to AlmaForge access is cleanest. Reusing an all-employees group generally is not what you want.
  • The public address of your AlmaForge Proxy Service, for example almaforge.example.com.
  • Outbound HTTPS egress from almad to login.microsoftonline.com from every auth host.
  • A break-glass procedure in place and tested.

Step 1. Register in Entra ID​

Sign in to the Microsoft Entra admin center and open Identity → Applications → App registrations → New registration.

Fill in the form:

  • Name: something recognizable, for example AlmaForge.
  • Supported account types: Accounts in this organizational directory only (single tenant). Multi-tenant and personal-account options work too, but single-tenant is the default shape for an internal access platform.
  • Redirect URI: select Web and enter https://almaforge.example.com/api/v1/oidc/callback, substituting your own proxy host.

Click Register.

On the application Overview page, copy these two values. You will need them in the connector YAML:

  • Application (client) ID.
  • Directory (tenant) ID.

Step 2. Create a client secret​

In the application sidebar, open Certificates & secrets → Client secrets → New client secret. Give it a description and an expiration period that matches your rotation policy, then click Add.

Copy the Value column immediately

Entra masks the secret as soon as you leave the page and never shows it again. The Secret ID column is not the secret. Do not use it.

Treat the client secret as a password

Store the client secret in the same secret manager you use for production credentials. Do not commit it to Git.

Step 3. Add the groups claim​

AlmaForge maps Entra security groups to roles by reading the groups claim on the ID token. By default Entra does not include this claim. You have to opt in.

In the application sidebar, open Token configuration → Add groups claim. Choose:

  • Security groups (sufficient for most deployments).
  • Under ID, select Group ID. This tells Entra to emit each group as its immutable object ID.

Save the change.

Step 4. Apply the OIDCConnector​

Write a YAML file with the values you collected:

microsoft.yaml:

examples/sso/microsoft.yaml
# Microsoft Entra ID connector using immutable group object IDs.
apiVersion: almaforge.com/v1
kind: OIDCConnector
metadata:
name: microsoft
spec:
display: Microsoft
provider: microsoft
issuerURL: https://login.microsoftonline.com/<tenant-id>/v2.0
clientID: <application-client-id>
clientSecret: <client-secret-value>
usernameClaim: preferred_username
claimsToRoles:
# AlmaForge-Admins
- claim: groups
value: 44d56e07-85fc-4f1e-9a3b-0c2d6e5b7a19
roles: [admin, access]
# AlmaForge-Developers
- claim: groups
value: 7907db2a-2e6b-42c8-b5d0-1f8e3a94c6b2
roles: [access]

Replace:

  • <tenant-id> with the Directory (tenant) ID from Step 1.
  • <application-client-id> with the Application (client) ID from Step 1.
  • <client-secret-value> with the secret Value from Step 2.
  • The claimsToRoles entries with your own groups and AlmaForge roles. Each value is a group Object ID, copied from the group's Overview page in Entra. A display name will not match. Keep the name in a comment above the entry so the mapping stays readable.

A few things to notice about this example:

  • issuerURL includes /v2.0 at the end. Entra's v1 endpoint is deprecated and emits a slightly different iss claim that does not match the discovery document. Use v2.0.
  • usernameClaim: preferred_username matches the Entra user principal name (for example alice@example.com) rather than the immutable object ID. This is almost always what you want.
  • The Redirect URI you registered in Step 1 must match the URL AlmaForge actually sends: https://<proxy_public_addr>/api/v1/oidc/callback with port 443 omitted. Including or omitting the port (:443) counts as a mismatch.
Test break-glass before applying

AlmaForge has no local accounts. If this is the only connector, a wrong claim mapping or expired client secret prevents new logins. Confirm your break-glass procedure works before applying the connector to a production cluster.

Apply the connector:

Terminal
alma apply -f microsoft.yaml

Step 5. Verify the connector​

Open the cluster's audit log for your test account. See SSO verification for event fields and the distinction from service logs.

Sign out and log in through Microsoft:

Terminal
alma logoutalma login --proxy=almaforge.example.com

A browser opens the cluster's sign-in page. Choose Microsoft Entra ID. On the first sign-in, Entra may prompt the user to consent to the application's permissions. Admins can pre-consent for the whole tenant from the app's API permissions page. After authentication, alma prints the roles, logins, and expiration assigned to the session.

A successful first-time login emits a UserLogin event and the CertificateCreate event for the certificate it mints. Both carry the spec.type that names them and the payload beside it:

JSON
{
"kind": "AuditEvent",
"apiVersion": "almaforge.com/v1",
"spec": {
"id": "1a2b3c4d-5e6f-4071-8293-a4b5c6d7e8f9",
"index": 0,
"timestamp": "2026-03-30T15:58:58.999000Z",
"clusterName": "almaforge.example.com",
"code": "T1001I",
"type": "UserLogin",
"verb": "login",
"actor": {
"name": "alice@example.com",
"type": "user"
},
"status": {
"success": true
},
"userLogin": {
"method": "oidc",
"attributes": {
"connector_id": "microsoft",
"connector_type": "oidc",
"name": "Alice Example",
"request_id": "8f0c1e6a-2f7e-4a7a-9f1e-1f0b6d0f1a2b",
"username": "alice@example.com"
}
}
}
}
JSON
{
"kind": "AuditEvent",
"apiVersion": "almaforge.com/v1",
"spec": {
"id": "6d7e8f90-a1b2-43c4-95d6-e7f8091a2b3c",
"index": 0,
"timestamp": "2026-03-30T15:58:58.999000Z",
"clusterName": "almaforge.example.com",
"code": "TC000I",
"type": "CertificateCreate",
"verb": "create",
"certificateCreate": {
"certType": "user",
"identity": {
"user": "alice@example.com",
"roles": [
"admin"
],
"logins": [
"tester",
"ec2-user",
"root"
],
"kubernetesGroups": [
"system:masters"
],
"traits": {
"groups": [
"44d56e07-85fc-...",
"7907db2a-2e6b-..."
],
"name": [
"Alice Example"
],
"preferred_username": [
"alice@example.com"
]
}
}
}
}
}

Three things to notice in these two events:

  • spec.certificateCreate.identity.traits.groups: the group object IDs Entra emitted in the raw token, which is exactly what claimsToRoles matched against. Nothing else is added to this list. If the groups array is missing entirely, Step 3 was skipped or the user is over the group overage limit.
  • spec.certificateCreate.identity.traits.preferred_username: compare this claim with spec.certificateCreate.identity.user to check the usernameClaim: preferred_username mapping. If this field is missing or contains the wrong value, check the user's UPN in Entra.
  • spec.certificateCreate.identity.roles and spec.certificateCreate.identity.logins: confirm the expected roles and SSH logins. See SSO verification for the full identity.

After a successful login, you can also run alma get user/<username> -o yaml and inspect spec.traits.

Read the full audit-event breakdown on the generic OIDC verification section.

Troubleshooting​

AADSTS50011: reply URL does not match​

The Redirect URI registered in Step 1 does not match the URL AlmaForge sends as redirect_uri. AlmaForge sends https://<proxy_public_addr>/api/v1/oidc/callback with port 443 omitted. Register that exact string in Entra and re-test.

Login fails with invalid_client​

The client secret on the connector does not match what Entra has on file. The most common causes are a mistyped value, a secret that was rotated at Entra without re-applying the connector, or a secret that has expired (Entra secrets cap at 24 months). Create a fresh secret in Certificates & secrets, copy the Value column, and re-apply the connector. See Client secret rotation for the full procedure.

No roles match the login​

The login is rejected when no mapping assigns a role. Either the token does not carry the groups claim (Step 3 was skipped), or a claimsToRoles value is a group display name rather than a group Object ID. Set ALMA_LOG_LEVEL=debug on the Auth host, sign in again, and inspect the claims Entra actually sent:

Terminal
journalctl -u almad -f | grep 'Extracted claims'

The groups array holds object IDs, and every claimsToRoles value has to be one of them character for character. If the login succeeds but the stored identity still looks wrong, inspect the user record as described in Claims, traits, and role templates.

Admin group missing for a user with many groups​

The user hit Entra's group overage limit. Limit the emitted claim to groups assigned to the application and confirm the resulting ID token contains the required group IDs. A smaller claimsToRoles mapping alone cannot fix an omitted token claim. See Group overage.

Operations​

Lifecycle​

Apply connector changes before testing the next login. No almad restart is needed. See When connector changes take effect for discovery caching and IdP-side changes.

Client secret rotation​

Entra client secrets expire after at most 24 months. When a secret expires, token exchange fails and every sign-in through the connector returns an error. In the audit log, look for:

  • UserLogin audit events with spec.status.success set to false and a spec.status.error field containing invalid_client.
  • No new certificate for that failed login attempt. Use the login record for the connector context. CertificateCreate has no connector field.

An Entra app can hold multiple active client secrets at the same time, but OIDCConnector only accepts one clientSecret value. Keep both secrets valid in Entra while updating the connector:

  1. Create a new client secret in the app's Certificates & secrets blade. Do not delete the old one yet.
  2. Copy the new secret's Value column immediately.
  3. Run alma apply -f microsoft.yaml with the new value.
  4. Run a test login through the connector to confirm the new secret works.
  5. Delete the old secret in Entra.

Confirm logins from each auth host before retiring the old secret. Track the expiration date of the current secret in whatever system you use for certificate expiry alerting. Entra surfaces the expiration on the Client secrets page but does not page anyone when it expires.

High availability​

Every auth node calls the Entra token endpoint independently and caches the OIDC discovery document per-instance for one hour. See High availability on the generic OIDC page for the full list of per-instance assumptions.

Secret storage​

Load clientSecret from a file or environment variable using Secret storage, which also covers protection of inline credentials and backups.

Break-glass access​

Test break-glass access before relying on this connector. Use it when an expired secret, Entra outage, incorrect role mapping, or signing-key change blocks new logins.

Monitoring​

Monitor the audit log for login outcomes. For diagnostics, inspect the Auth journal for token validation and the Proxy journal for browser callbacks. On each service host, tail OIDC-related messages with:

Terminal
journalctl -u almad -f | grep -Ei 'microsoft|oidc'

Check UserLogin failures and their spec.status.error field. See SSO verification for event codes, certificate fields, and debug-only journal copies. Collect records from audit event storage for SIEM ingestion. Collect service diagnostics from the journal separately.

Removal​

Delete the connector resource:

Terminal
alma delete oidcconnector/microsoft

Existing sessions issued through the connector remain valid until their certificates expire. No new sessions will be issued.

On the Entra side, delete the App registration in Entra ID so the client ID and secret can no longer be used.

References​