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:

  1. Authentication — Microsoft confirms the user's identity.
  2. Group membership — AVA reads the groups claim 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:

ItemMaps toNotes
Application (client) IDAZURE_AD_CLIENT_ID
Directory (tenant) IDAZURE_AD_TENANT_IDForms the MSAL authority URL
A client secretAZURE_AD_CLIENT_SECRETCreate 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_URIMust 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 groups array inline and substitutes an overage reference instead. AVA treats a missing/malformed groups claim 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:

ConditionResult
ENGINEERING_GROUP_ID unset/emptyDenied (not-configured) — there is no "allow everyone" fallback
groups claim absent from the tokenDenied (claim-missing) — usually Step 3 was skipped, or groups overage
groups claim present but not a string arrayDenied (claim-malformed)
Array present, group ID not in itDenied (not-in-group)
Array includes your group IDAuthorized

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, see autotask-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

VariableRequiredEffect
AZURE_AD_CLIENT_IDRequired to bootApp registration client ID
AZURE_AD_CLIENT_SECRETRequired to bootApp registration client secret
AZURE_AD_TENANT_IDRequired to bootDirectory (tenant) ID
AZURE_AD_REDIRECT_URIRecommended (defaults to localhost)OAuth callback — must exactly match the app registration
ENGINEERING_GROUP_IDRequired to bootSecurity-group object ID; membership required, fail-closed
NEXTAUTH_SECRETRequired to bootSigns the session cookie (32+ chars; openssl rand -hex 32)

Troubleshooting

SymptomLikely cause
Microsoft error page before AVA loadsRedirect URI mismatch (Step 1) — compare character-for-character
access_denied for every engineerGroups claim not configured on the ID token (Step 3)
access_denied for one engineerNot 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 roleO365 email didn't match an AutoTask Resource at first sign-in
All sign-ins suddenly fail after months of workingClient secret expired — issue a new one and update AZURE_AD_CLIENT_SECRET

Next steps


Did this page help you?