Microsoft Entra ID (SSO)
Engineers sign in to the AVA dashboard with their existing Microsoft 365 accounts — no separate passwords to manage, and sign-in is restricted to a single Entra security group you control. This page walks through the app registration, the groups-claim token configuration, and how engineers map to AutoTask Resources.
The client chat page has no login at all (it is protected by reCAPTCHA and rate limiting instead — see recaptcha-email.md); Entra ID applies only to the engineer dashboard.
How the flow works
The OAuth flow is entirely server-side (@azure/msal-node) using the authorization-code flow with PKCE. The browser never holds Azure tokens — after Microsoft sign-in completes, AVA issues its own signed, HTTP-only session cookie (HS256 JWT, 24-hour expiry, signed with NEXTAUTH_SECRET). The requested scopes are openid, profile, email, User.Read — standard sign-in scopes, no directory-wide permissions.
Authorization is a two-gate check on every sign-in:
- Authentication — Microsoft confirms the user's identity.
- Group membership — AVA reads the
groupsclaim from the Entra ID token and requires it to contain your engineering group's object ID. This check is fail-closed (details below).
Step 1 — Create the app registration
In the Entra admin center: App registrations → New registration. Collect:
| Item | Maps to | Notes |
|---|---|---|
| Application (client) ID | AZURE_AD_CLIENT_ID | |
| Directory (tenant) ID | AZURE_AD_TENANT_ID | Forms the MSAL authority URL |
| A client secret | AZURE_AD_CLIENT_SECRET | Create under Certificates & secrets. Note the expiry date and calendar a rotation — an expired client secret breaks all engineer sign-in at once. |
| Redirect URI (Web platform) | AZURE_AD_REDIRECT_URI | Must be exactly https://<your-host>/api/auth/callback |
Redirect URI precision matters. The value on the app registration and the AZURE_AD_REDIRECT_URI env var must match character-for-character — scheme, host, and path. A mismatch produces a Microsoft-side error before AVA is ever reached. For local development, http://localhost:3000/api/auth/callback can be added as an additional redirect URI.
Step 2 — Create the engineer security group
Create (or choose) an Entra security group and add every engineer who should be able to use the dashboard. Note its object ID:
ENGINEERING_GROUP_ID=<group-object-id-guid>Membership in this group is the only thing that authorizes dashboard access. Adding an engineer to the group grants access on their next sign-in; removing them blocks new sign-ins immediately (their existing session cookie expires within 24 hours).
Step 3 — Emit the groups claim in the ID token
By default, Entra ID tokens do not include group membership. Configure the app registration to emit it:
App registration → Token configuration → Add groups claim → select Security groups, for the ID token.
Without this, every sign-in — including valid group members — is denied with access_denied. That is fail-closed behavior working as designed, and it is the single most common Entra setup miss.
Large-directory note (groups overage). If an engineer belongs to very many groups, Entra stops emitting the full
groupsarray inline and substitutes an overage reference instead. AVA treats a missing/malformedgroupsclaim as a denial (fail-closed), so those engineers would be locked out. If this applies to your directory, configure the app registration to emit groups assigned to the application only (and assign your engineering group to the app), which keeps the claim inline regardless of total membership count.
The fail-closed authorization contract
AVA's group check accepts a sign-in only when the ID token's groups claim is a string array that includes ENGINEERING_GROUP_ID. Every other condition denies, each with a distinct reason in the server logs:
| Condition | Result |
|---|---|
ENGINEERING_GROUP_ID unset/empty | Denied (not-configured) — there is no "allow everyone" fallback |
groups claim absent from the token | Denied (claim-missing) — usually Step 3 was skipped, or groups overage |
groups claim present but not a string array | Denied (claim-malformed) |
| Array present, group ID not in it | Denied (not-in-group) |
| Array includes your group ID | Authorized |
There is no configuration that weakens this: a misconfigured deployment locks engineers out rather than letting anyone in.
Engineer → AutoTask Resource mapping
On an engineer's first sign-in, AVA auto-creates their engineer record and looks up an AutoTask Resource whose email matches their O365 sign-in email, storing the mapping. This is how time entries and ticket assignments attribute to the right person in AutoTask.
Practical implications:
- Make each engineer's O365 sign-in email match their AutoTask resource email before their first login. The comparison is by email address.
- If no matching resource is found, the engineer still signs in — chats work normally; time entries just fall back to the default role (
AUTOTASK_DEFAULT_ROLE_ID, seeautotask-setup.md) without per-resource attribution. - The lookup runs at first sign-in. If you create the AutoTask resource after an engineer's first login, the stored mapping stays unmapped — plan to have resources in place before onboarding engineers.
- There is no separate admin onboarding flow: being in the Entra group is the onboarding. Repeat sign-in for each engineer and you're done.
The Resources entity read that powers this lookup is part of the AutoTask security-level grants in autotask-setup.md Step 2.
Environment variable summary
| Variable | Required | Effect |
|---|---|---|
AZURE_AD_CLIENT_ID | Required to boot | App registration client ID |
AZURE_AD_CLIENT_SECRET | Required to boot | App registration client secret |
AZURE_AD_TENANT_ID | Required to boot | Directory (tenant) ID |
AZURE_AD_REDIRECT_URI | Recommended (defaults to localhost) | OAuth callback — must exactly match the app registration |
ENGINEERING_GROUP_ID | Required to boot | Security-group object ID; membership required, fail-closed |
NEXTAUTH_SECRET | Required to boot | Signs the session cookie (32+ chars; openssl rand -hex 32) |
Troubleshooting
| Symptom | Likely cause |
|---|---|
| Microsoft error page before AVA loads | Redirect URI mismatch (Step 1) — compare character-for-character |
access_denied for every engineer | Groups claim not configured on the ID token (Step 3) |
access_denied for one engineer | Not a member of the group, or groups overage for that user (see the large-directory note) |
| Sign-in works but time entries use the default role | O365 email didn't match an AutoTask Resource at first sign-in |
| All sign-ins suddenly fail after months of working | Client secret expired — issue a new one and update AZURE_AD_CLIENT_SECRET |
Next steps
autotask-setup.md— the AutoTask side of resource mapping and everything else PSA.configuration.md— the full environment variable reference.quickstart.md— first engineer login is Step 9 of the bring-up.
Updated 15 days ago