Azure Entra

Microsoft Entra ID (Azure AD) integration. Two responsibilities:

  1. SSO — every @renewa.de user authenticates via Microsoft; the login UI offers no password path for internal users.
  2. Workforce sync — internal employees, departments, locations, and Team groups are Entra-owned and synced on a schedule. Neither mocks nor migrations may re-seed them (spec: docs/superpowers/specs/2026-06-09-mock-people-defer-to-entra-design.md).

Architecture

ComponentPath
Auth routesbackend/src/routes/entra-auth.ts (GET /api/auth/entra/login, GET /api/auth/entra/callback)
Auth servicebackend/src/services/entra/entra-auth-service.ts
User sync servicebackend/src/services/entra/entra-sync-service.ts
Group sync servicebackend/src/services/entra/entra-group-sync-service.ts
Group read servicebackend/src/services/entra/entra-group-service.ts
Graph API clientbackend/src/services/entra/graph-client.ts
Scheduled jobbackend/src/lib/jobs/processors/entra-sync.ts (BullMQ, hourly at :30 — lib/jobs/schedulers.ts)
CLIbackend/src/db/entra-sync-cli.ts (db:entra-sync)
Frontend callbackfrontend/src/pages/AuthCallback.tsx

SSO Flow

  1. User enters their email on the login page
  2. Frontend calls POST /api/auth/check-domain; for @renewa.de the backend answers authMethod: 'entra'
  3. Frontend redirects to GET /api/auth/entra/login (returnUrl preserved via sessionStorage)
  4. User authenticates with Microsoft; Azure redirects back to /api/auth/entra/callback
  5. Backend exchanges the code for tokens, validates claims, links/creates the Users account, issues the session
  6. Frontend AuthCallback page restores the return URL and redirects into the app

Because the UI routes every @renewa.de email to SSO, browser-automation sessions (Playwright / e2e bots) authenticate via the API login endpoint instead — see the e2e-bot pattern in the repo CLAUDE.md.

Scheduled Workforce Sync

EntraSyncService runs hourly (BullMQ scheduler, cron 30 * * * *) and also via db:entra-sync, which runs before db:mock on local/pr-preview/development:

  • Syncs members of the security group ENTRA_SYNC_GROUP_ID from Microsoft Graph: creates new users, updates existing ones, deactivates users removed from the group
  • “Entra always wins”: Entra data overwrites RENEWA contact fields on every sync; email conflicts are logged and skipped
  • Auto-creates departments and locations from Entra attributes (spec: docs/superpowers/specs/2026-04-20-entra-department-auto-create-design.md)
  • An in-memory mutex prevents concurrent sync runs

EntraGroupSyncService syncs Entra “Team*” groups and memberships into entra_groups / entra_group_members:

  • Full sync hourly alongside the user sync
  • JIT sync of a single user’s groups at login
  • Owners who are also members get the owner role; unknown Entra OIDs are skipped

Role Mapping

Members of the optional ENTRA_ADMIN_GROUP_ID group get internalRole: 'admin'; everyone else synced from the group is employee. Checked both at login (entra-auth-service.ts) and during scheduled sync (entra-sync-service.ts) via Graph checkMemberGroups.

Redirect URI Management

Each environment needs its redirect URI (/api/auth/entra/callback) registered on the Azure app registration:

EnvironmentURI
Localhttp://localhost:<nginx-port>/api/auth/entra/callback
Developmenthttps://renewa-app-development.fly.dev/api/auth/entra/callback
Staging / Productionsame pattern on renewa-app-staging / renewa-app-production
PR Previewhttps://renewa-app-pr-<N>.fly.dev/api/auth/entra/callback

Managed via make sso-registerscripts/manage-entra-redirect-uri.sh (runs automatically as part of make dev-init). See PR Preview Deployments and Makefile Commands.

Configuration

SecretPurpose
ENTRA_TENANT_IDOrganization directory
ENTRA_CLIENT_IDApp registration identifier
ENTRA_CLIENT_SECRETApp authentication (Graph + OAuth)
ENTRA_SYNC_GROUP_IDSecurity group whose members are synced as employees
ENTRA_ADMIN_GROUP_IDOptional — members get the admin role

Secrets live in Infisical (EU) and sync natively to Fly.io (not GitHub Environment Secrets). See Deployment Pipeline.

User Provisioning

On first SSO login the system links the Azure identity (Entra OID) to an existing Users account with the Microsoft email, or creates a new user from the Azure profile. The scheduled sync provisions employees ahead of their first login, so most internal users already exist.