Skip to content

Directory Sync with Entra AD

When a company runs phishing simulations against 50, 500 or 5,000 employees, keeping the contact list current by hand isn’t viable. PhishSpot connects to Microsoft Entra ID (formerly Azure AD) and pulls users and groups directly from your directory. New hires show up, leavers get disabled, group membership reflects today’s org chart — without anyone editing a spreadsheet.

This chapter covers the integration setup, the sync schedule, what gets imported and how, manual syncs, and the history log you check when something looks off.

The alternative is CSV imports (Chapter 5 Contacts covers the manual paths). Those are fine for proof-of-concept or one-shot pilots, but for production they get stale fast. Directory sync gives you:

  • Authoritative source. Your IT team already maintains Entra. PhishSpot inherits that work — no parallel list to keep in sync.
  • Automatic onboarding. A new hire whose Entra account is created today shows up in PhishSpot tomorrow morning (or sooner, depending on schedule), and immediately becomes a valid target for autopilot campaigns.
  • Leavers handled cleanly. When IT disables an Entra account, the matching PhishSpot Contact is marked disabled — it stays in the database for historical reporting, but autopilots stop targeting it.
  • Groups follow. Membership in “Engineering”, “Finance”, “Zarząd” — whatever you’ve defined in Entra — is mirrored into PhishSpot Groups, so autopilot audiences track org structure automatically.

If you also want users to sign in to their personal training portal with Microsoft, the same integration backs that flow — see Chapter 24 SSO with Microsoft 365.

Integrations index showing the Microsoft Entra ID card alongside Google Workspace and the Spam Filter Whitelist

  1. Open Account settings → Integrations. You’ll see a grid of integration cards. Find the Microsoft Entra ID card; it shows a gray status dot (“inactive”) until connected.
  2. Click Connect to Microsoft. PhishSpot prompts you for your Entra tenant ID (a GUID, e.g., 1f3a8d2e-...). You can find this in the Entra admin centre under “Properties”.
  3. Submit. PhishSpot generates an HMAC-signed state token and redirects you to the Microsoft admin-consent screen at login.microsoftonline.com/<tenant>/v2.0/adminconsent. You must be signed in as a Global Administrator in the target tenant — admin consent grants the PhishSpot enterprise application directory-read scopes on behalf of all users.
  4. The consent screen lists the requested scopes:
    • User.Read.All — read user profiles (for the contact list).
    • Group.Read.All — read group definitions.
    • Directory.Read.All — read group memberships.
    • openid / profile / email — needed for the SSO sign-in flow.
  5. Grant. Microsoft redirects back to PhishSpot’s callback endpoint with admin_consent=True&tenant=<ID>&state=<HMAC>. PhishSpot verifies the HMAC, stores the tenant-scoped app token and turns the integration status to active.

You’re now connected. The Microsoft card shows an emerald status dot, the tenant ID in monospace, and the consent timestamp.

Click Manage on the active Microsoft card to open the integration settings. Two checkboxes and one dropdown:

  • Sync users to contacts — on by default. Pulls every Entra user (excluding guests and disabled accounts) and upserts them as PhishSpot Contact rows.
  • Sync groups — on by default. Pulls every Entra group (security and Microsoft 365 groups) and upserts them as PhishSpot Group rows. Memberships are reconciled in the same pass.
  • Schedule — one of:
    • Off — no automatic sync. You can still trigger sync manually (§25.5).
    • Hourly — runs every hour on the hour.
    • Daily — runs once a day at 02:00 UTC. Default for production tenants.
    • Weekly — runs once a week, on Mondays at 02:00 UTC.

Save settings. The platform’s scheduler (ScheduledDirectorySyncsJob) fan-outs at each interval, queuing one DirectorySyncJob per active integration matching that schedule.

The first sync after a fresh connection is usually the largest — it imports everything. Subsequent syncs only touch what changed (a handful of users updated, one group created, one membership removed), so they’re fast — typically under a minute even for thousands of users.

For each Entra user the importer creates or updates a Contact row keyed by the Entra object ID (oid). The mapping is:

PhishSpot Contact fieldEntra source
emailuserPrincipalName (falls back to mail)
first_name, last_namegivenName, surname
titlejobTitle
departmentdepartment
locationofficeLocation (city + country fallback)
telephonemobilePhone (falls back to businessPhones)
external_idid (the Entra OID)
external_stateactive if accountEnabled=true, else disabled
sourcealways :entra
synced_atsync timestamp

For each Entra group the importer creates a Group row keyed by group OID:

PhishSpot Group fieldEntra source
nameslugified displayName (e.g., “Dział IT” → dzial-it)
display_namedisplayName (preserves casing and Polish characters)
external_idgroup id
source:entra

Membership is reconciled per sync: PhishSpot lists all current members of each Entra group, removes any local ContactGroup rows whose contacts are no longer in the Entra group, and creates new ones for additions.

Important: Contacts whose Entra account is deleted (not just disabled) are not removed from PhishSpot — they’re marked external_state: disabled so historical reports remain intact. To purge a contact entirely, delete it from the PhishSpot UI manually.

The integration management page has a Sync now button. Click it to enqueue a DirectorySyncJob immediately, regardless of the configured schedule. Use this for:

  • Initial connection — most admins click Sync now once right after consent so the first import doesn’t wait for tomorrow’s 02:00 UTC cron.
  • Onboarding push — after IT marks 20 new hires in Entra, you want them in PhishSpot before your next scheduled run.
  • Troubleshooting — to retry after a transient Microsoft Graph error visible in the activity log.

A manual sync writes a DirectorySyncLog entry with trigger: manual — useful for distinguishing intentional admin actions from cron-driven runs when auditing.

Below the settings, the activity table shows the last 50 sync runs. Columns:

ColumnWhat it shows
StartedWhen the run kicked off
TriggerManual (admin clicked Sync now), Scheduled (cron), or OAuth callback (auto-sync right after admin consent)
StatusRunning, Success, Failed, or Partial (with color-coded badge)
ChangesUsers created / updated / disabled, groups created / updated, memberships changed
DurationWall-clock time for the run

A typical activity table for a small organisation looks like (real example from a working setup):

StartedTriggerStatusChangesDuration
6 hours agoScheduledSuccess0c · 7u · 0d / 0c · 0u / 1m22 s
2 days agoScheduledSuccess1c · 2u · 1d / 0c · 0u / 2m19 s
7 days agoManualSuccess0c · 4u · 0d / 0c · 0u / 0m14 s
8 days agoScheduledFailed1 s
10 days agoScheduledSuccess1c · 3u · 0d / 0c · 0u / 1m16 s

Failed runs expand to show the Microsoft Graph error message — most are transient rate-limit responses (HTTP 429) that the next scheduled run absorbs cleanly.

Entra integration management page — sync settings + activity history

Connect button takes me to a “consent denied” screen. You signed in as a non-admin user in your tenant. Sign out of all Microsoft accounts, sign in fresh as a Global Administrator, and retry.

Sync runs but no contacts appear. Open the activity log. If status is Success and changes are all zero, your Entra tenant has no users matching the filter (guests and disabled accounts are skipped). Verify in Entra that the users you expect have accountEnabled=true.

“Tenant mismatch” on callback. The Entra tenant ID you entered doesn’t match the tenant whose admin granted consent. Disconnect and reconnect with the correct tenant ID.

Partial syncs. If a sync finishes with status Partial, some upserts succeeded and others failed — usually because one user record violated a uniqueness constraint (e.g., two Entra users with the same email). Check the activity log entry; the error message includes the affected emails.

Schedule is “Off” but I expected Daily. Schedule defaults to the value you saved last — there’s no platform-wide default. New connections are created with Off so you can review settings before automation starts.