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?