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 fullgroupsarray inline and
substitutes an overage reference instead. AVA treats a missing/malformed
groupsclaim 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 about 1 hour ago