Configuring Azure AD OIDC for Backstage

Organizations standardized on Microsoft Entra ID (formerly Azure AD) want their portal to authenticate against the same directory as everything else, so a developer signs in once and the portal already knows their identity and groups. This how-to registers Backstage as an Entra application, wires the OIDC provider, and maps Entra groups onto catalog identity. It is an Entra-specific application of OIDC & SSO Configuration within Authentication, RBAC & Security Governance.

Prerequisites

  • Backstage 1.20+ reachable over HTTPS at a stable URL.
  • Rights to register an application in your Entra ID tenant, or an admin who will.
  • Decide up front whether the portal reads group claims, since that changes the app’s API permissions.
  • Client credentials injected at runtime as ${AZURE_CLIENT_ID}, ${AZURE_CLIENT_SECRET}, ${AZURE_TENANT_ID} — never committed.
Prerequisites for Entra OIDC An HTTPS portal, app-registration rights, a group-claims decision, and injected credentials enable Entra sign-in. HTTPS portal app-registration rights group-claims decision credentials injected Entra sign-in one directory
Deciding whether to read group claims up front matters — group overage on large directories forces a Graph API lookup instead of a token claim.

Exact Configuration

  1. Register the app in Entra. Set the redirect URI to the portal’s OIDC handler and note the client and tenant IDs.

    Redirect URI: https://portal.example.com/api/auth/microsoft/handler/frame
    Supported account types: single tenant
    API permissions: openid, profile, email, User.Read (+ GroupMember.Read.All if reading groups)
    
  2. Wire the Microsoft provider in Backstage config.

    # app-config.yaml
    # Requires @backstage/plugin-auth-backend-module-microsoft-provider >= 0.2.0
    auth:
      environment: production
      providers:
        microsoft:
          production:
            clientId: ${AZURE_CLIENT_ID}
            clientSecret: ${AZURE_CLIENT_SECRET}
            tenantId: ${AZURE_TENANT_ID}
            additionalScopes: ['GroupMember.Read.All']
    
  3. Resolve the signed-in user to a catalog identity so ownership and RBAC line up.

    // packages/backend/src/authResolver.ts
    // Match the Entra email to a User entity
    signInResolver: async ({ profile }, ctx) =>
      ctx.signInWithCatalogUser({ filter: { 'spec.profile.email': profile.email } }),
    
Entra sign-in flow The browser authenticates with Entra, the portal receives a token with claims, and the resolver maps it to a catalog user. browser Entra ID token + claims sign-in resolver catalog user owner + RBAC
The resolver is the bridge — an Entra identity is only useful once it maps to a catalog User the rest of the portal understands.

Validation

# Requires curl, jq, and a test account
# 1. The provider metadata is reachable
curl -s "https://login.microsoftonline.com/${AZURE_TENANT_ID}/v2.0/.well-known/openid-configuration" \
  | jq '.issuer'
# Expected: the tenant issuer URL

# 2. Sign-in resolves to a catalog user (check portal logs after login)
grep -q "signInWithCatalogUser" backend.log && echo "resolver matched a user"
# Expected: resolver matched a user

# 3. Group claims arrive when requested
#    Decode the ID token and look for the groups claim
echo "${ID_TOKEN}" | cut -d. -f2 | base64 -d 2>/dev/null | jq 'has("groups")'
# Expected: true when GroupMember.Read.All is granted
Entra OIDC validation checks Reachable metadata, a resolved catalog user, and present group claims confirm the integration. Metadata reachable issuer resolves User resolved catalog match Groups present when requested
A sign-in that succeeds but resolves no catalog user leaves the person authenticated yet ownerless — always verify the resolver matched.

Edge Cases & Troubleshooting

Symptom Root Cause Resolution
Redirect mismatch error Registered URI differs from the portal’s handler Match the redirect URI exactly, including the path
Sign-in works, no identity Resolver finds no matching catalog user Ingest users from Entra or relax the resolver filter
Group claim missing GroupMember.Read.All not consented Grant and admin-consent the permission
Groups truncated Group overage past the token limit Read groups via the Graph API instead of the claim
Token audience rejected Wrong tenant or client ID Confirm tenantId and clientId match the registration
Entra OIDC pitfalls by class Redirect and audience errors are registration issues; missing or truncated groups are claims issues. Registration redirect mismatch audience rejected → match URI + IDs Claims group missing group overage → consent + Graph lookup
Group overage is the classic large-tenant trap — past the claim limit Entra sends a link, not the groups, so plan the Graph fallback.

Frequently Asked Questions

Do I need group claims, or is sign-in enough?

If your RBAC and ownership derive from Entra groups, you need them; if identity alone drives access, you can skip them and avoid the Graph permission entirely. Reading groups adds an admin-consented permission and, on large tenants, a Graph lookup to handle overage — so only opt in when groups actually feed authorization.

How does this differ from the Okta setup?

The OIDC mechanics are identical — register an app, wire the provider, resolve to a catalog user — only the provider module and permission model differ. If you have already configured Okta for Backstage, the Entra setup is the same shape with Microsoft-specific group-overage handling layered on top.