Windows Server for beginners. Part 31: How does Entra Connect work?

All parts of the series “Windows Server for beginners”

For thirty parts the directory has lived inside the network: Kerberos, LDAP, SMB, Group Policy — none of it travels across the internet, and none of it was designed to. But the organization's mail is in Microsoft 365, its documents are in SharePoint, and you sign in to them through Entra ID, Microsoft's cloud directory, which knows nothing about Active Directory. Two directories with the same people in them mean two passwords, two places where people are hired and let go, and guaranteed drift. Entra Connect ties them together: accounts from the domain appear in the cloud, there is one password, and disabling someone in the domain disables them in the cloud too. This part is about what exactly goes to the cloud, how the cloud checks a password without a domain controller, and what has to be prepared in the directory for that.


Two directories

Active Directory Entra ID
Where your domain controllers a tenant example.onmicrosoft.com in Microsoft's cloud
Protocols Kerberos, NTLM, LDAP, SMB, RPC OAuth 2.0, OpenID Connect, SAML, WS-Federation; all over HTTPS
Structure forest, domains, OUs a flat list of the tenant's objects; groups, administrative units
Managing machines Group Policy, joining the domain Intune and device registration
Who authenticates a domain controller Entra ID; for hybrid users, one of the three methods below
What for everything inside the network Microsoft 365, Azure, apps that sign in with Microsoft, Conditional Access, MFA

The cloud's protocols work differently from Kerberos. OAuth 2.0 is a protocol for issuing access tokens: an application gets a signed token from Entra ID with rights to a specific API and presents it in an HTTP header. OpenID Connect is a layer on top of OAuth 2.0 for sign-in: next to the access token there is a token with the user's name (the ID token). SAML and WS-Federation are older XML protocols for the same purpose: the sign-in server issues a signed assertion about the user, and the browser carries it to the application. In all four the application never sees the password and never talks to a domain controller: it trusts Entra ID's signature. Conditional Access is a set of Entra ID rules that demand an extra condition at sign-in (MFA, a managed device, a country), and MFA is multi-factor authentication: besides the password, a confirmation in a phone app or with a security key. Intune is a cloud device management service: policies, apps and compliance without Group Policy.

Hybrid identity means that an account is created and lives in Active Directory (the source of authority), and Entra ID holds a synchronized copy of it that can't be edited in the cloud: the name, groups, attributes and state all come from the domain. The synchronization tool is Entra Connect Sync (Azure AD Connect until 2023): an application on a domain server with a synchronization engine inherited from Microsoft Identity Manager. There is also a lightweight option, Entra Cloud Sync: an agent on a server with the logic in the cloud; it's simpler to install and fits small and multi-forest environments, but lacks some features (device writeback, complex rules), and this series is about Connect.


What is synchronized

Objects: users, groups, contacts and, with hybrid join, computers. Attributes: given name, surname, display name, UPN, mail and proxyAddresses, phone numbers, title, department, manager, extensionAttribute1…extensionAttribute15, group membership, userAccountControl (an account disabled in the domain is disabled in the cloud too) — about a hundred and fifty attributes in all are read by the directory connector. Direction: from the domain to the cloud; back only what is explicitly enabled: the password after self-service reset in the cloud (password writeback), devices, groups.

Not synchronized: passwords in clear text (see below), the OU structure (the cloud has none), policies, sIDHistory, the account expiry date accountExpires (an account that has expired in the domain keeps working in the cloud), local attributes such as homeDirectory. The SID travels as the onPremisesSecurityIdentifier attribute, but the cloud doesn't use it to check access. Password hashes are synchronized only for objects of class user, not inetOrgPerson.

The anchor. The cloud has to know that the object arriving today is the same one as yesterday, even if it was renamed or moved. For that Entra Connect computes an immutable sourceAnchor for every object, and the cloud stores it as immutableId (onPremisesImmutableId in Microsoft Graph). The source of the anchor is chosen at installation; later it can only be switched from objectGUID to mS-DS-ConsistencyGuid (the wizard's Configure Source Anchor task). By default it's the mS-DS-ConsistencyGuid attribute: on the first synchronization Entra Connect writes the object's objectGUID into it and from then on relies only on it. That is why the attribute is separate from objectGUID: objectGUID changes when an object is moved to another forest, while mS-DS-ConsistencyGuid can be copied along with the object, keeping the link to the cloud. The wizard also lets you choose objectGUID itself — that's how installations were done before 2017, and many old ones still are.

PS C:\> Get-ADUser alice -Properties mS-DS-ConsistencyGuid | Select-Object ObjectGUID, @{n='ConsistencyGuid'; e={[guid]$_.'mS-DS-ConsistencyGuid'}}, @{n='immutableId'; e={[Convert]::ToBase64String($_.'mS-DS-ConsistencyGuid')}}

ObjectGUID                           ConsistencyGuid                      immutableId
----------                           ---------------                      -----------
9c3f1a2b-7d4e-4f60-8a1b-2c3d4e5f6a7b 9c3f1a2b-7d4e-4f60-8a1b-2c3d4e5f6a7b Kxo/nE59YE+KGyw9Tl9qew==

The Base64 of the GUID's bytes is exactly the immutableId you see in the cloud.


Three ways to sign in

The user types a password on Microsoft's sign-in page. The cloud has no domain controller. Three options for who checks it:

Password hash synchronization (PHS). Every two minutes an agent on the sync server asks a domain controller for changed password hashes (the unicodePwd attribute) using MS-DRSR, the same replication protocol controllers use between themselves (part 10). unicodePwd holds the NT hash — MD4 of the password in UTF-16, the same thing as the RC4 key in Kerberos. The controller hands it over encrypted with the RPC session key, the agent decrypts it and sends to the cloud not the hash itself but the result of PBKDF2 — a function that runs its input through HMAC-SHA256 many times to make brute force expensive: the NT hash plus a ten-byte per-user salt, a thousand iterations. The cloud stores that result and at sign-in runs the typed password through the same steps. The original NT hash never reaches the cloud, so a value stolen from the cloud is useless for a pass-the-hash attack in the domain — signing in with the hash instead of the password. It works when the domain is unavailable, lets the cloud report leaked passwords, and needs nothing on the network side. It's the recommended method, and the rest of this part uses it.

Pass-through authentication (PTA). No hashes go to the cloud; agents on domain servers keep an outbound connection to the cloud, receive the password from it (encrypted with the agent's key) and check it with a domain controller. The cloud stores nothing, but signing in to the cloud depends on the agents and the domain being available. For organizations that aren't allowed to hand over hashes.

Federation. The cloud doesn't check the password at all; it redirects the user to the organization's own server — AD FS (Active Directory Federation Services, a Windows Server role) or a third-party provider — over SAML or WS-Federation, and that server issues a signed token after checking. The most flexible (smart cards, custom conditions) and the heaviest: a server farm, proxies, certificates, and the cloud is unavailable whenever the farm is. Microsoft keeps moving customers from federation to PHS with Conditional Access.

PHS can also be enabled alongside PTA or federation as a fallback, and that's the usual recommendation.


UPNs and domains

Users sign in to the cloud with their UPN: alice@example.com. The UPN suffix must be a verified domain of the tenant — one with a TXT record in public DNS that proves ownership. corp.example.com can't be verified without example.com, and company.local never can. Users with an unverified suffix get a cloud UPN like alice@example.onmicrosoft.com, which causes confusion for years.

So before synchronization: example.com is verified in the tenant, the suffix is added to the forest, and every user to be synchronized gets a new UPN:

PS C:\> Set-ADForest -Identity corp.example.com -UPNSuffixes @{ Add = 'example.com' }
PS C:\> Get-ADUser -SearchBase 'OU=Departments,DC=corp,DC=example,DC=com' -Filter 'Enabled -eq $true' |
    ForEach-Object { Set-ADUser $_ -UserPrincipalName "$($_.SamAccountName)@example.com" }
PS C:\> Get-ADUser alice | Select-Object UserPrincipalName

UserPrincipalName
-----------------
alice@example.com

This doesn't affect signing in to the domain: CORP\alice and the Kerberos principal alice@CORP.EXAMPLE.COM stay the same, and the controller accepts alice@example.com as a UPN (part 3). Ideally the UPN matches the primary mail address, so users know one name.

Before installation the directory is checked with Microsoft's IdFix tool: duplicate proxyAddresses and UPNs, invalid characters, empty required attributes. The cloud would reject such objects during synchronization; IdFix shows them in advance.


Installation

The server is sync1.corp.example.com: a member server with the full graphical interface (Server Core isn't supported), Microsoft recommends Windows Server 2022 or 2025, with outbound HTTPS to Microsoft's addresses. You can install it on a domain controller, but then the controller takes on the sync server's duties as well. New versions of the AzureADConnect.msi installer are published only in the Microsoft Entra admin center. The wizard runs in Customize mode (express mode synchronizes the whole forest and gives the sync account generous rights — fine for a lab, not for an organization):

  1. Components: the ADSync service account — a virtual account NT SERVICE\ADSync by default; when installed on a domain controller the wizard creates a managed service account ADSyncMSA…; you can also specify a group managed service account from part 13. The database is SQL Server Express LocalDB (a 10 GB limit, about 100,000 objects) or a full SQL Server.
  2. User sign-in: Password Hash Synchronization, with the Enable single sign-on box checked.
  3. Connect to Entra ID: an account with the Hybrid Identity Administrator role, assigned directly rather than through a group. The account itself isn't kept on the server: the wizard registers an application in the tenant, and the server signs in to the cloud with that application's certificate over OAuth 2.0 (client credentials). The certificate lives on the server — in the TPM (a hardware module the private key can't be extracted from) if there is one — is valid for 90 days and rotates by itself. All new installations work this way starting with version 2.5.76; older ones must move to version 2.6.84 or later and to application sign-in by April 7, 2027, or synchronization will stop.
  4. Connect to the directory: the forest corp.example.com, an Enterprise Admin account — for installation only; the wizard creates its own account.
  5. Uniquely identifying users: "Let Azure manage the source anchor" — the wizard picks mS-DS-ConsistencyGuid as the anchor if that attribute isn't populated on anything in the directory yet.
  6. Filtering: domains and OUs — only OU=Departments and OU=Groups; service accounts, administrators and OU=Servers don't belong in the cloud.
  7. Optional features: password writeback for self-service reset, directory extensions, Exchange hybrid — as needed.
  8. Seamless SSO: domain administrator credentials to create the AZUREADSSOACC computer account.

What the wizard left in the directory:

PS C:\> Get-ADUser -Filter 'Name -like "MSOL_*"' -Properties Description | Format-List Name, Description

Name        : MSOL_a1b2c3d4e5f6
Description : Account created by Microsoft Azure Active Directory Connect with installation identifier a1b2c3d4e5f64c7d8e9fa0b1c2d3e4f5 running on computer SYNC1 configured to synchronize to tenant example.onmicrosoft.com. This account must have directory replication permissions in the local Active Directory and write permission on certain attributes to enable Hybrid Deployment.

PS C:\> (Get-Acl 'AD:\DC=corp,DC=example,DC=com').Access | Where-Object IdentityReference -like '*MSOL_*' | Select-Object ActiveDirectoryRights, ObjectType, InheritedObjectType, InheritanceType

      ActiveDirectoryRights ObjectType                           InheritedObjectType                  InheritanceType
      --------------------- ----------                           -------------------                  ---------------
               ReadProperty 00000000-0000-0000-0000-000000000000 f0f8ffac-1191-11d0-a060-00aa006c33ed     Descendents
ReadProperty, WriteProperty 00000000-0000-0000-0000-000000000000 4828cc14-1437-45bc-9b07-ad6f015e5f28     Descendents
ReadProperty, WriteProperty 00000000-0000-0000-0000-000000000000 bf967a9c-0de6-11d0-a285-00aa003049e2     Descendents
              ExtendedRight 1131f6ad-9c07-11d1-f79f-00c04fc2dcd2 00000000-0000-0000-0000-000000000000            None
ReadProperty, WriteProperty 00000000-0000-0000-0000-000000000000 bf967aba-0de6-11d0-a285-00aa003049e2     Descendents
              ExtendedRight 1131f6aa-9c07-11d1-f79f-00c04fc2dcd2 00000000-0000-0000-0000-000000000000            None
ReadProperty, WriteProperty 00000000-0000-0000-0000-000000000000 5cb41ed0-0e4c-11d0-a286-00aa003049e2     Descendents
              WriteProperty 5b47d60f-6090-40b2-9f37-2a4de88f3063 5df2b673-6d41-4774-b3e8-d52e8ee9ff99     Descendents
              ExtendedRight 00299570-246d-11d0-a768-00aa006e0529 bf967aba-0de6-11d0-a285-00aa003049e2     Descendents

The GUID in ObjectType is a right or an attribute, the one in InheritedObjectType is the class of objects the entry applies to. These are the rights of an account created by the wizard in express mode, with every feature that was ever enabled:

Entry What it grants What for
1131f6aa… and 1131f6ad… on the domain root Replicating Directory Changes and Replicating Directory Changes All reading changes and password hashes through replication (PHS)
read and write all properties of user (bf967aba…), inetOrgPerson (4828cc14…), group (bf967a9c…), contact (5cb41ed0…) any attribute of these objects Exchange hybrid writeback
00299570… for user Reset Password password writeback
write msDS-KeyCredentialLink (5b47d60f…) for msDS-Device (5df2b673…) device keys Windows Hello key writeback
read all properties of publicFolder (f0f8ffac…) Exchange public folders mail-enabled public folder sync

The two rights on the domain root are exactly what a controller needs to replicate the directory, passwords included. Writing all properties of users is more than any single feature needs: with it you can rewrite the servicePrincipalName or msDS-KeyCredentialLink of any unprotected user and sign in as them. The wizard grants rights but doesn't take them back when a feature is turned off. That's why in an organization the connector account is created by hand and given only the rights of the enabled features — with the AdSyncConfig module from the installation folder (more on it in part 32).

The MSOL_… account with these rights and the application certificate the server signs in to the tenant with make sync1 a tier 0 server from part 15: whoever controls it controls both the domain and the cloud. Microsoft explicitly requires treating it as a tier 0 component. Access to it is the same as to a domain controller.


Synchronization

The engine works in cycles: every 30 minutes a delta (changes since last time), and password hashes every two minutes in a separate stream. The ADSync module on the sync server:

PS C:\> Get-ADSyncScheduler

AllowedSyncCycleInterval            : 00:30:00
CurrentlyEffectiveSyncCycleInterval : 00:30:00
CustomizedSyncCycleInterval         :
NextSyncCyclePolicyType             : Delta
NextSyncCycleStartTimeInUTC         : 15.10.2026 15:00:12
PurgeRunHistoryInterval             : 7.00:00:00
SyncCycleEnabled                    : True
MaintenanceEnabled                  : True
StagingModeEnabled                  : False
SchedulerSuspended                  : False
SyncCycleInProgress                 : False

PS C:\> Get-ADSyncConnector | Select-Object Name, Type

Name                          Type
----                          ----
example.onmicrosoft.com - AAD Extensible2
corp.example.com              AD

Two connectors — to the directory and to the cloud — and between them the metaverse: the engine's own database, into which objects are imported from both sources, joined by rules and exported from. One cycle is six steps, three per connector: importing changes, synchronization (applying the rules to the metaverse) and export. The run history is kept for seven days (PurgeRunHistoryInterval):

PS C:\> Get-ADSyncRunProfileResult -NumberRequested 6 | Select-Object ConnectorName, RunProfileName, Result, StartDate

ConnectorName                 RunProfileName        Result  StartDate
-------------                 --------------        ------  ---------
corp.example.com              Export                success 15.10.2026 15:00:43
example.onmicrosoft.com - AAD Export                success 15.10.2026 15:00:36
example.onmicrosoft.com - AAD Delta Synchronization success 15.10.2026 15:00:35
corp.example.com              Delta Synchronization success 15.10.2026 15:00:34
example.onmicrosoft.com - AAD Delta Import          success 15.10.2026 15:00:29
corp.example.com              Delta Import          success 15.10.2026 15:00:28

An extra cycle is started like this, and -PolicyType Initial instead of Delta gives a full cycle that runs every object through the rules again:

PS C:\> Start-ADSyncSyncCycle -PolicyType Delta

Result
------
Success

Success here only means "the cycle was queued"; the result is in the run history. While a cycle is running, Get-ADSyncConnectorRunStatus shows the active connector and step; between cycles it outputs nothing.

Not everything in the ADSync module works remotely. Get-ADSyncScheduler, Get-ADSyncConnector and Get-ADSyncPartitionPasswordSyncState answer through Invoke-Command too (part 14), but the run history, rules, global settings and connector space objects work only in a session opened on the server itself. From a remote session, over WinRM or SSH, they all answer the same way:

Get-ADSyncRunProfileResult: There was no endpoint listening at net.pipe://localhost/ADSyncManagement that could accept the message. This is often caused by an incorrect address or SOAP action. See InnerException, if present, for more details.

Rules. Objects are carried from the connectors into the metaverse and back by synchronization rules: inbound ones from the directory and the cloud into the metaverse, outbound ones from the metaverse to the cloud and the directory; each has a precedence (a lower number wins) and expressions for attributes. There are several dozen built-in rules:

PS C:\> Get-ADSyncRule | Group-Object Direction | Select-Object Name, Count

Name     Count
----     -----
Inbound     23
Outbound    30

Your own rules are added in the Synchronization Rules Editor when you need, say, to put a value from extensionAttribute3 into the cloud department. The built-in ones aren't edited but overridden by your own with a lower precedence number: an Entra Connect upgrade overwrites the built-in rules. The Synchronization Service Manager console (miisclient.exe) shows every cycle: how many objects were added and changed, and what errors there were — it's the first place to look when "the user didn't show up".

Which anchor was chosen and what else was enabled at installation is in the server's global settings:

PS C:\> (Get-ADSyncGlobalSettings).Parameters | Where-Object Name -in 'Microsoft.SynchronizationOption.AnchorAttribute', 'Microsoft.UserSignIn.SignOnMethod', 'Microsoft.UserSignIn.DesktopSsoEnabled', 'Microsoft.OptionalFeature.ExportDeletionThreshold', 'Microsoft.OptionalFeature.ExportDeletionThresholdValue', 'Microsoft.Synchronize.ServerConfigurationVersion' | Select-Object Name, Value

Name                                                   Value
----                                                   -----
Microsoft.UserSignIn.DesktopSsoEnabled                 True
Microsoft.OptionalFeature.ExportDeletionThresholdValue 500
Microsoft.SynchronizationOption.AnchorAttribute        objectGUID
Microsoft.Synchronize.ServerConfigurationVersion       2.6.92.0
Microsoft.OptionalFeature.ExportDeletionThreshold      True
Microsoft.UserSignIn.SignOnMethod                      PasswordHashSync

This server was installed with objectGUID as the anchor. That can happen without an explicit choice: in "Let Azure manage the source anchor" mode the wizard takes mS-DS-ConsistencyGuid only if the attribute isn't populated on any object in the directory (or if a previous installation stored its choice in the tenant), and if a previous synchronization has already populated it, the wizard concludes that another application uses it and takes objectGUID. In that case synchronization doesn't write mS-DS-ConsistencyGuid at all, and each object's anchor is the Base64 of its objectGUID. A default installation has mS-DS-ConsistencyGuid in AnchorAttribute. The rest: sign-in through hash synchronization, Seamless SSO on, and a deletion threshold of 500 objects per cycle (more on it in part 32).

Filtering. The OU list is changed in the directory connector's properties — in the wizard (the Customize synchronization options task) or in the console — and is visible in its settings:

PS C:\> (Get-ADSyncConnector -Name 'corp.example.com').Partitions.ConnectorPartitionScope | Format-List ContainerInclusionList, ContainerExclusionList

ContainerInclusionList : {OU=Departments,DC=corp,DC=example,DC=com, OU=Groups,DC=corp,DC=example,DC=com}
ContainerExclusionList : {CN=LostAndFound,DC=corp,DC=example,DC=com, DC=corp,DC=example,DC=com}

The domain root is excluded and only two OUs are included. After a change a full cycle is needed, Start-ADSyncSyncCycle -PolicyType Initial. Filtering by attribute — a rule that sets cloudFiltered = True in the metaverse — cuts objects off inside an OU, for example everyone with extensionAttribute15 = NoSync. Built-in filtering works the same way: the In from AD - User Join rule itself keeps krbtgt_…, MSOL_…, AAD_… and Exchange system mailboxes out of the cloud.

A second server is installed in staging mode: it imports and computes everything the same way but doesn't export. If the primary fails, staging mode is turned off on the second one in the wizard (the Configure staging mode task) and the primary is put into it; there is no cmdlet to switch the mode, and Get-ADSyncScheduler only shows it in StagingModeEnabled. Two active servers at once are not allowed.

Moving to another server goes through the same staging mode. Every time the settings are changed in the wizard, the server saves them to %ProgramData%\AADConnect as an Applied-SynchronizationPolicy-<date>-<time>.json file; changes made with PowerShell or the rules editor don't end up there and are exported with the wizard's View or export current configuration task. On the new server the wizard in Customize mode with the Import synchronization settings box checked takes this file and turns staging mode on by default. Credentials aren't carried over from the file: the directory account and the cloud sign-in are set up again. Once the new server's results match the old one's, the old server is put into staging mode, the new one is taken out of it, and only then is the old one removed.

Removing Entra Connect from the old server doesn't remove its accounts:

  • the directory keeps its MSOL_… — the description names the computer and the installation identifier — together with the replication rights on the domain root. If the new server created its own account, the old one is deleted, and its entries in the domain root's permission list then remain as rules for an unknown SID and are removed separately;
  • in the cloud, installations with the old sign-in method leave a Sync_<SERVER>_<identifier>@example.onmicrosoft.com account with the Directory Synchronization Accounts role — one per server, staging servers included; a tenant can have no more than 20 of them. Listing and removing them takes the Microsoft Graph PowerShell module:
PS C:\> Connect-MgGraph
PS C:\> $role = Get-MgDirectoryRole | Where-Object DisplayName -eq 'Directory Synchronization Accounts'
PS C:\> Get-MgDirectoryRoleMember -DirectoryRoleId $role.Id | Select-Object Id, @{n='UserPrincipalName'; e={$_.AdditionalProperties.userPrincipalName}}
PS C:\> Remove-MgUser -UserId <Id of the old server's account>

A server that signs in to the cloud with an application has no such account; how it signs in is shown by Get-ADSyncEntraConnectorCredential on the server itself:

PS C:\> Get-ADSyncEntraConnectorCredential | Select-Object ConnectorIdentityType

ConnectorIdentityType
---------------------
Application

After a server is moved from the old method to an application, Microsoft recommends removing its former account with Remove-ADSyncAADServiceAccount -AADCredential (Get-Credential) -Name Sync_<SERVER>_<identifier>, using a cloud administrator's credentials. The documentation doesn't say what to do with a decommissioned server's application; it can be found in the tenant's app registrations by the identifier the same Get-ADSyncEntraConnectorCredential showed on the old server, and an unused application with a valid certificate is better deleted.

To stop synchronization during maintenance — Set-ADSyncScheduler -SyncCycleEnabled $false; forgetting to turn it back on is the classic reason "new employees aren't getting mail".


Seamless SSO

A user on a domain machine opens Outlook in the browser and doesn't type a password. For that, installation created the AZUREADSSOACC computer account in the directory with the SPN HTTP/autologon.microsoftazuread-sso.com, and its Kerberos key (the password the wizard set) was handed to the cloud:

PS C:\> Get-ADComputer AZUREADSSOACC -Properties servicePrincipalName | Select-Object -ExpandProperty servicePrincipalName
HTTP/www.tm.a.prd.aadg.akadns.net
HTTP/www.tm.a.prd.aadg.trafficmanager.net
HTTP/aadg.windows.net.nsatc.net
HTTP/autologon.prda.aadg.msidentity.com
HTTP/autologon.microsoftazuread-sso.com
RestrictedKrbHost/www.tm.a.prd.aadg.akadns.net
RestrictedKrbHost/www.tm.a.prd.aadg.trafficmanager.net
RestrictedKrbHost/aadg.windows.net.nsatc.net
RestrictedKrbHost/autologon.prda.aadg.msidentity.com
RestrictedKrbHost/autologon.microsoftazuread-sso.com

The browser asks for a ticket to the name it sees in the address, autologon.microsoftazuread-sso.com; the other SPNs are internal names of the same Microsoft service behind a load balancer, there so that the ticket is found under any of them.

The rest is exactly part 6: the browser, redirected by the sign-in page to https://autologon.microsoftazuread-sso.com/… with a 401 Negotiate challenge, asks the domain controller for a ticket to HTTP/autologon.microsoftazuread-sso.com, gets it (encrypted with the AZUREADSSOACC key) and sends it to the cloud; the cloud, knowing the same key, decrypts the ticket, reads the UPN and SID from it and issues a session. The domain controller is an ordinary KDC, the cloud is an ordinary service with its own key, and the only unusual thing is that the service is outside the network.

The client-side requirement is the same as for the intranet site in part 30: the address must be in the Local intranet zone, otherwise the browser won't send the ticket. The Site to Zone Assignment policy with the entry https://autologon.microsoftazuread-sso.com → zone 1, using part 24:

PS C:\> $gpo = New-GPO -Name 'Browser - Intranet zone'
PS C:\> Set-ADObject "CN={$($gpo.Id)},CN=Policies,CN=System,DC=corp,DC=example,DC=com" -Replace @{
    gPCUserExtensionNames = '[{35378EAC-683F-11D2-A89A-00C04FBBCFA2}{D02B1F73-3407-48AE-BA88-E8213C6761F1}][{4CFB60C1-FAA6-47F1-89AA-0B18730C9FD3}{D02B1F73-3407-48AE-BA88-E8213C6761F1}]' }
PS C:\> $key = 'HKCU\Software\Policies\Microsoft\Windows\CurrentVersion\Internet Settings'
PS C:\> Set-GPRegistryValue -Guid $gpo.Id -Key $key -ValueName 'ListBox_Support_ZoneMapKey' -Type DWord -Value 1
PS C:\> Set-GPRegistryValue -Guid $gpo.Id -Key "$key\ZoneMapKey" -ValueName 'https://autologon.microsoftazuread-sso.com' -Type String -Value 1
PS C:\> Set-GPRegistryValue -Guid $gpo.Id -Key "$key\ZoneMapKey" -ValueName 'https://intranet.corp.example.com' -Type String -Value 1
PS C:\> New-GPLink -Guid $gpo.Id -Target 'DC=corp,DC=example,DC=com' -LinkEnabled Yes

Each Set-GPRegistryValue outputs the policy object with its new modification time, and New-GPLink outputs the link it created. It's the same policy the intranet needed in part 30: ZoneMapKey with the value 1 means the Local intranet zone. Edge and Chrome take zones from Windows, just like Internet Explorer. The policy is a user policy: users can sit in different OUs, and zones are a setting of the user's profile.

The Set-ADObject line isn't superfluous. ZoneMapKey is only where the template's list is stored: by itself it doesn't change any zones. The working entries such as …\Internet Settings\ZoneMap\Domains\microsoftazuread-sso.com\autologon with the value https = 1 are built from it by a separate Group Policy client-side extension (CSE) — "Internet Explorer Zonemapping", the iedkcs32.dll library, GUID {4CFB60C1-FAA6-47F1-89AA-0B18730C9FD3}. For a GPO the client runs only the extensions listed in its gPCUserExtensionNames attribute (gPCMachineExtensionNames for the computer part): pairs of "extension GUID, editor snap-in GUID". The Group Policy editor adds Zonemapping there by itself when it saves the Site to Zone Assignment List; Set-GPRegistryValue only knows the Registry extension {35378EAC-…}. Without fixing the attribute the policy applies, gpresult shows it, the value sits in ZoneMapKey in the registry, yet the address stays in the Internet zone, and the browser shows a password form instead of sending a ticket. Set-ADObject comes before Set-GPRegistryValue so that the following write bumps the GPO version and clients that have already seen it process the policy again.

The check on the client after gpupdate /target:user /force — that the entry has appeared where Windows reads it:

PS C:\> Get-ItemProperty 'HKCU:\Software\Policies\Microsoft\Windows\CurrentVersion\Internet Settings\ZoneMap\Domains\microsoftazuread-sso.com\autologon' | Select-Object https

https
-----
    1

A browser started before the policy update rereads the zones only after a restart.

Encryption types. The ticket is encrypted with the AZUREADSSOACC key, and the cloud must have a key of the same type. If the account has no msDS-SupportedEncryptionTypes attribute, the wizard and the key rollover hand the cloud only an RC4 key, while domain controllers on Windows Server 2025 don't issue RC4 tickets: the ticket arrives in AES-256, the cloud can't decrypt it and answers AADSTS81004: Kerberos authentication failed. So before the account's first rollover, allow only AES (24 = AES128 + AES256), then roll the key over:

PS C:\> Set-ADComputer AZUREADSSOACC -Replace @{ 'msDS-SupportedEncryptionTypes' = 24 }

The AZUREADSSOACC key should be changed every 30 days — like the password of any computer account, but here there's no automation: Update-AzureADSSOForest from the AzureADSSO module on the sync server, with domain and tenant administrator credentials. The module from Entra Connect 2.6.92 doesn't work in PowerShell 7 (it needs WCF, Windows Communication Foundation — the .NET Framework library for calling web services, which current .NET doesn't have), and in Windows PowerShell 5.1 it trips over the version of the Microsoft.IdentityModel.Abstractions assembly required by MSAL — Microsoft's library for obtaining OAuth tokens from Entra ID: the Entra Connect wizard works around this with a binding redirect in its config, the console doesn't. Before a rollover, check whether this is fixed in the installed version. The account has the same tier 0 properties: whoever has its key can mint a ticket to the cloud as any user.

Checking without a browser. Whether the cloud accepts the ticket can be seen without the sign-in page: curl.exe on Windows speaks Negotiate and asks the controller for a ticket by itself.

PS C:\> klist purge
PS C:\> curl.exe --negotiate -u : -s -o NUL -w '%{http_code}' "https://autologon.microsoftazuread-sso.com/example.com/winauth/sso?client-request-id=$([guid]::NewGuid())"
200

klist purge first discards the tickets in the session cache so that curl requests a fresh one. 200 means the cloud decrypted the ticket; 403 with AADSTS81004 in the response body means the key in the cloud doesn't match the key in the directory or the ticket's encryption type. After a rollover the cloud needs a few minutes. curl sends the ticket to any address and doesn't check zones, so 200 here while the browser shows a password form means the problem is the zone on the client.

What Seamless SSO doesn't do. It replaces only typing the password. If Conditional Access or security defaults require multi-factor authentication (MFA), the confirmation on the phone after a ticket sign-in remains: to the cloud a Kerberos ticket is the same first factor as a password. And in private windows — InPrivate in Edge, Incognito in Chrome — browsers by default don't send Kerberos to any address, so test sign-in in a regular profile.

For Windows 10 and 11 Seamless SSO is a fallback: machines with hybrid join (part 32) sign in to the cloud with a device token at Windows sign-in, and the browser needs nothing. Seamless SSO remains for older Windows, for machines without hybrid join and for Firefox without integration.


Looking at it by hand

The object from both sides. What the cloud knows about a synchronized user, the sync server keeps locally — in the cloud connector's space, a copy it imports every cycle. The way to it goes from the directory object through the metaverse:

PS C:\> $ad = Get-ADSyncCSObject -ConnectorName 'corp.example.com' -DistinguishedName 'CN=Alice Smith,OU=Sales,OU=Departments,DC=corp,DC=example,DC=com'
PS C:\> $mv = Get-ADSyncMVObject -Identifier $ad.ConnectedMVObjectId
PS C:\> $dn = ($mv.Lineage | Where-Object ConnectorName -like '*- AAD').ConnectedCsObjectDN
PS C:\> $cs = Get-ADSyncCSObject -ConnectorName 'example.onmicrosoft.com - AAD' -DistinguishedName $dn
PS C:\> $cs.Attributes | Where-Object Name -in 'userPrincipalName', 'sourceAnchor', 'cloudAnchor', 'cloudMastered', 'onPremisesSamAccountName', 'onPremiseSecurityIdentifier', 'onPremisesDistinguishedName', 'accountEnabled' | Select-Object Name, @{n='Value'; e={$_.Values -join ', '}}

Name                        Value
----                        -----
accountEnabled              true
cloudAnchor                 User_5d1e7c2a-3b4f-4e6a-9c8d-7f0a1b2c3d4e
cloudMastered               false
onPremiseSecurityIdentifier AQUAAAAAAAUVAAAAx/f+13x3VciUWs4BUAQAAA==
onPremisesDistinguishedName CN=Alice Smith,OU=Sales,OU=Departments,DC=corp,DC=example,DC=com
onPremisesSamAccountName    alice
sourceAnchor                Kxo/nE59YE+KGyw9Tl9qew==
userPrincipalName           alice@example.com

sourceAnchor matches the Base64 of mS-DS-ConsistencyGuid; cloudAnchor is the object's identifier in the cloud (User_ plus its id in Microsoft Graph); onPremiseSecurityIdentifier is the SID from part 3 in binary form; cloudMastered = false means the domain owns the object and it can't be edited in the cloud. From the cloud side the Microsoft Graph PowerShell module shows the same through the OnPremisesSyncEnabled, OnPremisesImmutableId, OnPremisesSecurityIdentifier, OnPremisesSamAccountName and OnPremisesLastSyncDateTime properties of Get-MgUser; it needs a sign-in to the tenant with the User.Read.All permission:

PS C:\> Connect-MgGraph -Scopes 'User.Read.All' -NoWelcome
PS C:\> Get-MgUser -UserId alice@example.com -Property UserPrincipalName, OnPremisesSyncEnabled, OnPremisesImmutableId, OnPremisesLastSyncDateTime, OnPremisesSamAccountName, OnPremisesSecurityIdentifier | Format-List UserPrincipalName, OnPremises*

As long as OnPremisesSyncEnabled = True, the object can't be edited in the cloud: every change is made in the domain.

Password synchronization. When the agent last finished successfully:

PS C:\> Get-ADSyncPartitionPasswordSyncState | Where-Object DN -ne 'default' | Format-List DN, PasswordSyncLastSuccessfulCycleEndTimestamp, PasswordSyncLastCycleStatus

DN                                          : DC=corp,DC=example,DC=com
PasswordSyncLastSuccessfulCycleEndTimestamp : 15.10.2026 15:02:33
PasswordSyncLastCycleStatus                 : Successful

The time is UTC, and it moves every two minutes even if no passwords changed: the agent asks the controller on a schedule. According to the documentation, a password change of a particular user leaves a pair of events from the Directory Synchronization source in the sync server's Application log: 656 "Password Change Request" and 657 "Password Change Result", with the user's anchor and DN:

PS C:\> Invoke-Command -ComputerName sync1 { $ids = 656, 657; Get-WinEvent -FilterHashtable @{LogName='Application'; ProviderName='Directory Synchronization'; Id=$ids} -MaxEvents 2 }

The array of IDs is passed in a variable: inside Invoke-Command, Windows PowerShell 5.1 finds no events with the literal Id=656, 657.

Password synchronization as the controller sees it. If Directory Service Access auditing is enabled on the controller, every two minutes three 4662 events from the MSOL_… account appear in the Security log — on the domain object itself (class domainDNS), with replication rights:

PS C:\> Invoke-Command -ComputerName dc1 {
    Get-WinEvent -FilterHashtable @{LogName='Security'; Id=4662; StartTime=(Get-Date).AddMinutes(-3)} |
        Where-Object { $_.Properties[1].Value -like 'MSOL_*' } |
        Select-Object TimeCreated, @{n='Object'; e={$_.Properties[6].Value}}, @{n='Right'; e={($_.Properties[11].Value -split '\s+' | Where-Object { $_ })[1]}}
} | Format-Table TimeCreated, Object, Right

TimeCreated         Object                                  Right
-----------         ------                                  -----
15.10.2026 18:02:33 %{6b1f2e3d-4c5a-4b69-8d7e-0f1a2b3c4d5e} {1131f6ad-9c07-11d1-f79f-00c04fc2dcd2}
15.10.2026 18:02:33 %{6b1f2e3d-4c5a-4b69-8d7e-0f1a2b3c4d5e} {1131f6ad-9c07-11d1-f79f-00c04fc2dcd2}
15.10.2026 18:02:33 %{6b1f2e3d-4c5a-4b69-8d7e-0f1a2b3c4d5e} {1131f6aa-9c07-11d1-f79f-00c04fc2dcd2}

Object is the domain's objectGUID ((Get-ADDomain).ObjectGUID), Right is Replicating Directory Changes All twice and Replicating Directory Changes once. This is exactly how a DCSync attack looks in the log, when an attacker with these rights pulls hashes as if they were a controller. So DCSync detection rules must know this account and this server as an exception, and the rights themselves must stay with this account only.

The Seamless SSO ticket on a workstation after signing in to Outlook in the browser:

PS C:\> klist | Select-String -Pattern 'autologon' -Context 0,2

>       Server: HTTP/autologon.microsoftazuread-sso.com @ CORP.EXAMPLE.COM
        KerbTicket Encryption Type: AES-256-CTS-HMAC-SHA1-96
        Ticket Flags 0x40a10000 -> forwardable renewable pre_authent name_canonicalize

An ordinary service ticket issued by our KDC for a service that lives in Microsoft's cloud.

The anchor in the directory's metadata. Who wrote mS-DS-ConsistencyGuid, and when, is visible in the replication metadata (part 10):

PS C:\> repadmin /showobjmeta dc2 "CN=Alice Smith,OU=Sales,OU=Departments,DC=corp,DC=example,DC=com" | Select-String ConsistencyGuid

20481533                                Office\DC2   6118432 2026-10-15 14:58:07    1 mS-DS-ConsistencyGuid

Version 1: the attribute was written once, on the first export to the directory, and should never change again. With an objectGUID anchor there is no such line at all. What happens when objects in the two directories have to be matched by hand, when they didn't match, and which accounts synchronization steers around — that's the last part.


Sources

Entra Connect and hybrid identity:

The anchor and attributes:

Sign-in methods:

Synchronization:

Seamless SSO:

Auditing and the cloud side:


The cloud now knows the same users as the domain and lets them in with the same password or the same Kerberos ticket. But the link between the two directories rests on matching rules, and they don't always fire the way people expect.

Next — part 32: account matching and its pitfalls. Soft matching by UPN and address and hard matching by the anchor; what to do when the cloud already has accounts with the same names; protected accounts and AdminSDHolder, which synchronization stumbles over; password writeback; hybrid device join; and what not to do with an object that has a twin in the cloud.

Only registered users can leave a comment.

Sign in or register to leave a comment.