Skip to main content

Microsoft Entra ID

Use an Entra app role to decide who can reach your application. This walkthrough connects one workforce tenant, grants ordinary portal access to signed-in users, and requires the App.Access role for /app.

It targets caddy-security v1.3.0 with go-authcrunch v1.3.8. The configuration keyword remains driver azure. Local OIDC fixtures check the released driver and policies; complete the login tests in your own tenant to verify consent, assignments, and Conditional Access.

Before you start​

Complete Install and verify. You need permission to register an application and manage its enterprise application's assignments, two test users, and a public HTTPS portal. This example uses https://auth.example.com/auth/; replace the hostname throughout.

The main walkthrough uses one Microsoft Entra workforce tenant in the public cloud. Other account types have different requirements. The OAuth and OIDC overview explains how the provider, portal, and application policy fit together.

Register the application​

In the Microsoft Entra admin center, select the correct directory, then open Entra ID → App registrations → New registration. Use these settings:

SettingValue
NameA recognizable application name, such as AuthCrunch Example
Supported account typesAccounts in this organizational directory only
Redirect platformWeb
Redirect URIhttps://auth.example.com/auth/oauth2/azure/authorization-code-callback

Earlier Azure portal application registration form with account type and Web redirect fields

Earlier Azure portal: this capture selected a broader account population. For the workforce walkthrough, choose Accounts in this organizational directory only and use the public callback above. Select the image to view it at full size.

After registration, copy Application (client) ID and Directory (tenant) ID from Overview. Use the directory's GUID for ENTRA_TENANT_ID. The callback's azure is the AuthCrunch realm, not the tenant ID. See Microsoft's app registration guide.

Under Certificates & secrets → Client secrets, create a secret and store its Value, not its Secret ID. Supply that value only to the AuthCrunch server. Track its expiration in your deployment's credential process. The example uses server-side authorization code flow with a client secret and S256 PKCE; it does not require the implicit-grant checkboxes or a public-client flow. See Microsoft's authorization code flow.

Define and assign application access​

In this app registration, open App roles → Create app role:

FieldValue
Display nameExample app member
Allowed member typesUsers/Groups
ValueApp.Access
DescriptionCan use the example application
EnabledYes

The role's Value is the string matched by AuthCrunch. Its display name and generated role ID are different values.

Open Entra ID → Enterprise apps, select the corresponding enterprise application, then Users and groups → Add user/group. Assign Alice to Example app member and leave Bob without that role. For this sign-in app, Entra emits the assigned value in the ID token's roles claim. See Microsoft's app role guide.

StageValue
Entra ID tokenroles: ["App.Access"]
Portal transformMatches realm azure and role App.Access
AuthCrunch tokenAdds app/member
Application policyRequires app/member

Review the enterprise application's Properties → Assignment required?. When it is Yes, Entra can reject an unassigned user before AuthCrunch receives a token. When it is No, an otherwise eligible user can sign in without an app role, letting you verify AuthCrunch's 403 response. Keep your organization's intended setting; do not broaden sign-in just to reproduce a test. A separate non-access role can admit a test user without granting App.Access when assignment is required. Use ordinary test accounts rather than tenant admins. See Entra assignment behavior.

Configure AuthCrunch​

Save this as Caddyfile and replace auth.example.com in the site address and policy URL. The complete file is embedded from the canonical Entra example.

Caddyfile
# Set the public portal hostname and ENTRA_TENANT_ID before use.
{
admin off
persist_config off

security {
oauth identity provider azure {
driver azure
realm azure
client_id {env.ENTRA_CLIENT_ID}
client_secret {env.ENTRA_CLIENT_SECRET}
scopes openid email profile
tenant_id {$ENTRA_TENANT_ID}
email claim check disabled
login icon text "Microsoft Entra ID"
}

authentication portal myportal {
enable identity provider azure
crypto default token lifetime 900
crypto key sign-verify {env.JWT_SHARED_KEY}
cookie path /

ui {
links {
"Example app" /app icon "las la-external-link-alt"
"My identity" /auth/whoami icon "las la-user"
}
}

transform user {
match realm azure
action add role authp/user
}

transform user {
match realm azure
match role App.Access
action add role app/member
}
}

authorization policy apppolicy {
set auth url https://auth.example.com/auth/
crypto key verify {env.JWT_SHARED_KEY}
allow roles app/member
}
}
}

auth.example.com {
route {
redir /auth /auth/ 308

authenticate /auth/* with myportal

@app path /app /app/*
route @app {
authorize with apppolicy
respond "You reached the Entra-protected app." 200
}

respond "AuthCrunch is running. Open /app to begin." 200
}
}

Set the environment:

export ENTRA_TENANT_ID='YOUR_DIRECTORY_TENANT_GUID'
export ENTRA_CLIENT_ID='YOUR_APPLICATION_CLIENT_ID'
export ENTRA_CLIENT_SECRET='YOUR_CLIENT_SECRET_VALUE'
export JWT_SHARED_KEY="$(openssl rand -hex 32)"

{$ENTRA_TENANT_ID} expands when the Caddyfile is parsed. The credentials and shared signing key use runtime {env.VARIABLE} placeholders. For a managed service, use its protected environment and preserve the signing key across restarts. Never put a real client secret in the Caddyfile or browser code.

The tenant ID selects the discovery document at https://login.microsoftonline.com/TENANT_GUID/v2.0/.well-known/openid-configuration. Inspect that document from the AuthCrunch host and confirm its issuer identifies your directory. The driver discovers the authorization, token, and signing-key endpoints and validates the ID token against the client ID and exact issuer.

./bin/authcrunch adapt --adapter caddyfile --config Caddyfile >/dev/null
./bin/authcrunch run --config Caddyfile

Adaptation verifies syntax; startup contacts the discovery/key endpoints. The host needs outbound HTTPS, public DNS, and working certificate issuance for the portal. The example disables the admin endpoint; stop with Ctrl+C and restart after edits.

Why email is optional here​

Entra does not guarantee an email claim for every account. The example requests openid email profile but uses app roles, not email, to authorize access. It therefore includes email claim check disabled in the provider block. Signature, issuer, audience, state, nonce, and PKCE checks remain enabled.

An optional email claim can improve display information, but requesting it cannot create an address the account does not have. preferred_username is not a substitute for a verified email, and this driver's claim parser does not copy it, oid, or tid into the portal token. The portal sub comes from the provider's app-specific subject. See Entra's ID-token claims and the released parser.

If another portal feature or transform requires email, configure and verify that claim separately. Removing email claim check disabled restores the driver's email-presence requirement; it does not verify ownership of an address.

Verify sign-in and denial​

  1. Open https://auth.example.com/app and choose Microsoft Entra ID.
  2. Sign in as Alice and complete any tenant consent or Conditional Access steps.
  3. Open My identity (/auth/whoami). Confirm realm azure and roles App.Access, authp/user, and app/member.
  4. Select Example app or reopen /app. Expect the protected response.
  5. Use a separate browser session for Bob. If Entra admits him without App.Access, verify a 403 on /app, /app/, and /app/nested. An Entra assignment rejection is a separate, earlier boundary.

Removing an assignment does not rewrite an existing AuthCrunch token. This example uses a 900-second token lifetime; obtain a new token when testing changed roles. Portal sign-out does not end the Microsoft SSO session in this configuration, so use isolated browser sessions to test different users.

After these checks, replace the protected respond with your application's reverse_proxy, keeping authorize before it and both /app matchers.

Group claims as an alternative​

If your deployment uses group IDs, configure the app registration's Token configuration → Add groups claim and include the needed groups in the ID token. With the default format, values are group object IDs, not display names. Replace the App.Access transform with a matcher for the exact emitted ID:

transform user {
match realm azure
match role "YOUR_GROUP_OBJECT_ID"
action add role app/member
}

AuthCrunch combines the ID token's groups into portal roles. Keeping both the group and app-role transforms grants access through either rule. To require both, put both matchers in one transform. See Microsoft's group-claim configuration.

Microsoft limits group lists in JWTs to 200 entries, including nested groups. Over that limit, the token can contain an overage indicator instead of the list. This release does not resolve Entra group overage through Microsoft Graph. A missing group must not grant app access; prefer the explicit app role or a suitably limited group claim. Adding Graph permissions alone does not implement the missing lookup.

Other account types​

Account populationConfiguration boundary
One workforce tenantUse the directory GUID as in this walkthrough. Guest users still need the intended assignment and app role.
Multiple workforce tenantsorganizations and common discovery return a templated issuer. This release compares issuer strings literally and does not implement tenant-template validation. Do not use the driver's default common as a working multitenant configuration.
Personal Microsoft accounts (Live, Xbox, Outlook.com)Register an app that supports personal accounts and use tenant_id consumers. Use an explicit account policy, not the workforce app-role setup above.

The public consumers discovery document supplies the fixed Microsoft-account issuer. To adapt the example for personal accounts, replace {$ENTRA_TENANT_ID} with consumers and replace the App.Access transform with:

transform user {
match realm azure
match sub "REPLACE_WITH_PERSONAL_ACCOUNT_SUB"
action add role app/member
}

Sign in to the portal first, copy the intended account's sub from /auth/whoami, update the matcher, restart, and log in again. Entra subjects are specific to the app registration. Check an unselected account is denied. Keep the email-presence exception if the account does not supply email.

Microsoft documents tenant and issuer validation; the release's validator explains the exact-match limit. An app registration accepting more account types does not add multitenant validation to this driver. External ID customer tenants, B2C policies, sovereign clouds, and live personal-account consent are outside the validated walkthrough.

Troubleshoot​

SymptomCheck
AADSTS50011 / redirect mismatchRegister the exact Web callback, including /auth/ and realm azure.
Client authentication failsUse the secret Value rather than Secret ID, the correct client ID, and an unexpired credential.
Issuer contains {tenantid}Set a concrete directory GUID instead of common or organizations; do not disable token checks.
Alice gets 403Verify her assignment to this enterprise application, role Value App.Access, and a fresh ID token for this client.
Bob is blocked by EntraCheck Assignment required and tenant policy; this is earlier than AuthCrunch's app policy.
An unassigned user reaches /appCheck for another grant of app/member or a policy that allows authp/user.
Email is missingExpected for some accounts; the example does not authorize by email. Check dependent portal features separately.
A group member gets 403Inspect the ID token for the group object ID or overage, and confirm the claim is included in the ID token.
Sign-in resumes after portal logoutThe Microsoft SSO session survives; use a separate browser session for another user.

Continue to Generic OpenID Connect for claim extraction and discovery details.