
Bereits der Exchange Server 2019 bekam mit CU13 die Option auf Modern Auth (OAuth 2.0). Anfangs eingeschränkt auf Outlook on the Web (OWA) / das Exchange Control Panel (ECP). Später mit CU14/15(?) kam Modern Auth für MAPI over HTTP (MAPI/HTTP), die Exchange Web Services (EWS), Exchange Active Sync (EAS) und das Offline Adressbook (OAB). IMAP, POP und Outlook Anywhere (RPC/HTTP)* gehen leer aus.
Protokoll | ADFS Modern Auth wird unterstützt |
MAPI over HTTP (MAPI/HTTP) | ✅ |
Outlook Anywhere (RPC/HTTP) | ❌ (*Deprecated in Exchange Server SE ab CU |
Outlook on the Web (OWA) | ✅ (claims-based authentication) |
Exchange Admin Center (ECP) | ✅ (claims-based authentication) |
Exchange Active Sync (EAS) | ✅ |
Exchange Web Services (EWS) | ✅ |
Offline Address Book (OAB) | ✅ |
Internet Message Access Protocol (IMAP) | ❌ |
Post Office Protocol (POP) | ❌ |
Was benötigen wir oder auch „Die Formalitäten“
- Einen Domain Controller (BFT-DC01)
- Domain-Admin User (Administrator)
- Einen Exchange Server (BFT-EXSE)
- Exchange-Admin User (Exch.Admin)
- Einen Active Directory Federation Services (ADFS) Server (BFT-ADFS01)
- Active Directory User mit lokalen Adminrechten auf dem ADFS Server (ADFS.Admin)
- Active Directory Gruppe (ADFS Admins)
- Windows 11 Client oder Windows Server 2025 Terminalserver mit Outlook
- Anforderungen an das Betriebssystem:
- Windows 11 22H2 mit KB5023706
- Windows Server 2025
- Kein Support / keine Funktion in Windows Server 2019 und Windows Server 2022!
- macOS Sequoia / iOS ab 17.6.1
- Anforderungen an Outlook
- M365 Apps im Semi Annual Channel ab Version 2402
- M365 Apps Monthly Enterprise Channel ab Version 2304
- M365 Apps im Current Channel ab Version 2304
- Outlook 2024 (Volume License / Retail) ab Version 2408
- Outlook 2021 (Retail) ab Version 2304; (Outlook 2021 (Volume License) hat keinen Support!)
- Outlook for Mac (Microsoft 365) ab Version 2504; (Outlook 2024 for Mac hat keinen Support!)
- Apple Mail ab macOS Sequoia / iOS 17.6.1
- Anforderungen an das Betriebssystem:
Eins Vorweg
Es ist eigentlich selbstverständlich, aber.. 😉 Bitte, bitte erstellt euch eine Testumgebung ähnlich eurer produktiven Umgebung(en) und geht die Scripte Schritt für Schritt durch. Macht dann gleich die (vermutlich) benötigten Anpassungen für eure Umgebung(en)! Die Scripte werden zwar (vermutlich) alles korrekt einrichten, ob und wie sinnvoll das aber in eure Umgebung(en) passt, müsste sich zeigen. 🤔
Meine Testumgebung(en) für diesen Beitrag
Der oder die Domain Controller, Exchange Server sowie Clients / Terminalserver inkl. passender Outlook Version setze ich einmal als vorhanden, aktuell gepatched und einsatzbereit voraus. Der ADFS Server ist idealerweise bereits Mitglied der Domäne und ebenfalls tagesaktuell gepatched. Sollte es noch keinen „ADFS.Admin“ oder die Gruppe „ADFS Admins“ geben, wird das Script diese anlegen (und unschön in den Users Container stecken). Hier kann in den ersten Zeilen (10 bis 15) in den Scripten die Benennung aller beteiligter User / Gruppen / Server angepasst werden. Am sinnvollsten dürfte es sein, die Benutzer / Gruppen / Server vorab an passender Stelle im Active Directory anzulegen.
Vorbereitung der Active Directory Domäne
Zur Vorbereitung der Active Directory Domäne kann das folgende Script „Prepare-ADFS-Domain.ps1“ genutzt werden. In den ersten Zeilen (12 bis 17) sollten Anpassungen erfolgen, die entsprechend auf eure Umgebung zu treffen. Im Anschluss geht das Script her und erstellt ein selbst signiertes Zertifikat für LDAPs, was für diesen Artikel eigentlich nicht notwendig ist. 😉 Das Zertifikat wird anschließend in ein neues Gruppenrichtlinienobjekt importiert, damit es auf allen Membern als vertrauenswürdig anerkannt wird. Anschließend wird ggfs. eine DNS Host Zone für den ADFS Service Name und der DNS A-Record erstellt. Sofern noch keine Group Managed Service Accounts im Spiel sind, wird ein neuer KDS Root Key und anschließend der gMSA für den ADFS Server erstellt. Sollte es den ADFS Admin Account sowie die Gruppe noch nicht geben, werden diese erstellt. Anschließend werden die für ADFS benötigten Organisations Einheiten – wie in Creating an AD FS farm without Domain Administrator privileges | Microsoft Learn beschrieben – angelegt und berechtigt. Die fertige „adminConfig“ findet sich dann in „C:\_install\ADFSAdminConfig.txt“ und wird im nächsten Schritt auf dem neuen ADFS Server benötigt.
# Prepare-ADFS-Domain.ps1
#
#
$objADDomain = Get-ADDomain -Current LocalComputer
$DNSServer = $objADDomain.PDCEmulator
$CertGPO = "C_Distribute Root certificates"
$ADFSGPO = "C_ADFS configuration and SSOn"
$ADFSUGPO = "U_ADFS configuration and SSOn"
# Start: Edit as needed
$ADFSServerAcc = "BFT-ADFS01"
$ADFSAdminAcc = "ADFS.Admin"
$ADFSAdminGroup = "ADFS Admins"
$ADFSSvcAcc = "gMSA-ADFS"
$ADFSFQDN = "adfs.binfordtools.net"
$ADFSIPAddress = "192.168.51.15"
# End: Edit as needed
if(-not (Test-Path -Path "C:\_install" -PathType Container)){
New-Item -Path "C:\" `
-Name "_install" `
-Type Directory |
Out-Null
}
Write-Host "Creating LDAPs / LDAP TLS certificate"
$Cert = New-SelfSignedCertificate -Subject "CN=$(-join($env:COMPUTERNAME, ".", $env:USERDNSDOMAIN)), O=$env:USERDNSDOMAIN, C=DE" `
-DnsName $(-join($env:COMPUTERNAME, ".", $env:USERDNSDOMAIN)), $env:USERDNSDOMAIN, $env:COMPUTERNAME `
-CertStoreLocation "cert:\LocalMachine\My" `
-NotAfter (Get-Date).AddYears(10) `
-KeyAlgorithm RSA `
-KeyLength 2048 `
-KeyExportPolicy Exportable
Write-Host "Exporting certificate to C:\_install\ADLDAÜSCert.cer"
Export-Certificate -Cert $Cert `
-FilePath "C:\_install\ADLDAPSCert.cer" |
Out-Null
Write-Host "Importing certificate from C:\_install\ADLDAÜSCert.cer to trusted roots"
Import-Certificate -FilePath "C:\_install\ADLDAPSCert.cer" `
-CertStoreLocation Cert:\LocalMachine\Root |
Out-Null
Write-Host "Checking for GPO `"$CertGPO`""
$objCertGPO = Get-GPO -All |
Where-Object { $_.DisplayName -eq $CertGPO }
if($null -eq $objCertGPO){
Write-Host "Creating GPO `"$CertGPO`""
$objCertGPO = New-GPO -Name $CertGPO
$objCertGPO.GpoStatus = "UserSettingsDisabled"
Write-Host "Linking GPO `"$CertGPO`" to `"$($objADDomain.DistinguishedName)`""
New-GPLink -Guid $objCertGPO.Id `
-Target $objADDomain.DistinguishedName `
-LinkEnabled Yes |
Out-Null
}
$certRegPath = "HKLM:\SOFTWARE\Microsoft\SystemCertificates\Root\Certificates\{0}" `
-f $Cert.Thumbprint
$certBlob = Get-ItemProperty -Path $certRegPath `
-Name "Blob" |
Select-Object Blob `
-Expand Blob
Write-Host "Adding LDAPs certificate to GPO `"$CertGPO`""
$certPolRegPath = "HKLM\SOFTWARE\Policies\Microsoft\SystemCertificates\Root\Certificates\{0}" `
-f $Cert.Thumbprint
Set-GPRegistryValue -Name $CertGPO `
-Key $certPolRegPath `
-ValueName "Blob" `
-Type Binary `
-Value $certBlob |
Out-Null
Write-Host "Checking for GPO `"$ADFSGPO`""
$objADFSGPO = Get-GPO -All |
Where-Object { $_.DisplayName -eq $ADFSGPO }
if($null -eq $objADFSGPO){
Write-Host "Creating GPO `"$ADFSGPO`""
$objADFSGPO = New-GPO -Name $ADFSGPO
$objADFSGPO.GpoStatus = "UserSettingsDisabled"
Write-Host "Linking GPO `"$ADFSGPO`" to `"$($objADDomain.DistinguishedName)`""
New-GPLink -Guid $objADFSGPO.Id `
-Target $objADDomain.DistinguishedName `
-LinkEnabled Yes |
Out-Null
}
Write-Host "Setting registry keys in GPO `"$ADFSGPO`" for Modern Auth"
Set-GPPrefRegistryValue -Guid $objADFSGPO.Id `
-Context Computer `
-Key "HKLM\SOFTWARE\Policies\Microsoft\AAD\AuthTrustedDomains\https://$ADFSFQDN/" `
-ValueName "(Default)" `
-Value "" `
-Type String `
-Action Update |
Out-Null
Set-GPPrefRegistryValue -Guid $objADFSGPO.Id `
-Context Computer `
-Key "HKLM\SOFTWARE\Policies\Microsoft\AAD\AuthTrustedDomains\https://$ADFSFQDN" `
-ValueName "(Default)" `
-Value "" `
-Type String `
-Action Update |
Out-Null
Write-Host "Checking for GPO `"$ADFSUGPO`""
$objADFSUGPO = Get-GPO -All |
Where-Object { $_.DisplayName -eq $ADFSUGPO }
if($null -eq $objADFSUGPO){
Write-Host "Creating GPO `"$ADFSUGPO`""
$objADFSUGPO = New-GPO -Name $ADFSUGPO
$objADFSUGPO.GpoStatus = "ComputerSettingsDisabled"
Write-Host "Linking GPO `"$ADFSUGPO`" to `"$($objADDomain.DistinguishedName)`""
New-GPLink -Guid $objADFSUGPO.Id `
-Target $objADDomain.DistinguishedName `
-LinkEnabled Yes |
Out-Null
}
Write-Host "Setting registry keys in GPO `"$ADFSUGPO`" for Modern Auth and SSOn"
Set-GPPrefRegistryValue -Guid $objADFSUGPO.Id `
-Context User `
-Key "HKCU\SOFTWARE\Microsoft\Office\16.0\Common\Identity" `
-ValueName "EnableExchangeOnPremModernAuth" `
-Value 1 `
-Type DWord `
-Action Update |
Out-Null
Set-GPPrefRegistryValue -Guid $objADFSUGPO.Id `
-Context User `
-Key "HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Internet Settings\ZoneMap\Domains\$(($ADFSFQDN -replace $ADFSFQDN.Split(".")[0]).TrimStart("."))\$($ADFSFQDN.Split(".")[0])" `
-ValueName "https" `
-Value 1 `
-Type DWord `
-Action Update |
Out-Null
Set-GPPrefRegistryValue -Guid $objADFSUGPO.Id `
-Context User `
-Key "HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Internet Settings\ZoneMap\EscDomains\$(($ADFSFQDN -replace $ADFSFQDN.Split(".")[0]).TrimStart("."))\$($ADFSFQDN.Split(".")[0])" `
-ValueName "https" `
-Value 1 `
-Type DWord `
-Action Update |
Out-Null
# When cert auth / device registration is needed:
# Create DNS record / host zone for
# - certauth.<domain>.<tld>
# - enterpriseregistration.<domain>.<tld>
$tmpDom = ($ADFSFQDN -replace $ADFSFQDN.Split(".")[0]).Substring(1)
$tmpZone = Get-DnsServerZone -ComputerName $DNSServer |
Where-Object { $_.ZoneName -eq $tmpDom }
if($null -eq $tmpZone){
Write-Host "Creating DNS Zone `"$ADFSFQDN`""
Add-DnsServerPrimaryZone -ComputerName $DNSServer `
-Name $ADFSFQDN `
-ReplicationScope Forest `
-DynamicUpdate None
Write-Host "Adding Record `".`" with `"$ADFSIPAddress`"to DNS Zone `"$ADFSFQDN`""
Add-DnsServerResourceRecordA -ComputerName $DNSServer `
-ZoneName $ADFSFQDN `
-Name "." `
-IPv4Address $ADFSIPAddress
} else{
Write-Host "Adding Record `"$($ADFSFQDN.Split(".")[0])`" with `"$ADFSIPAddress`"to DNS Zone `"$($tmpZone.ZoneName)`""
Add-DnsServerResourceRecordA -ComputerName $DNSServer `
-ZoneName $tmpZone.ZoneName `
-Name ($ADFSFQDN.Split(".")[0]) `
-IPv4Address $ADFSIPAddress
}
if($null -eq (Get-KdsRootKey)){
Write-Host "Adding new Kds root key"
Add-KdsRootKey -EffectiveTime (Get-Date).AddHours(-10) |
Out-Null
}
$ADFSSvc = New-ADServiceAccount -Name $ADFSSvcAcc `
-DnsHostName $ADFSFQDN `
-ServicePrincipalNames "http/$ADFSFQDN" `
-PassThru
$ADFSGroup = Get-ADGroup -Filter 'name -eq $ADFSAdminGroup'
if($null -eq $ADFSGroup){
Write-Host "Creating ADFS admin group `"$ADFSAdminGroup`""
$ADFSGroup = New-ADGroup -Name $ADFSAdminGroup `
-DisplayName $ADFSAdminGroup `
-Description $ADFSAdminGroup `
-GroupScope Global `
-PassThru
}
$ADFSAdmin = Get-ADUser -Filter 'name -eq $ADFSAdminAcc'
if($null -eq $ADFSAdmin){
Write-Host "Creating ADFS Admin `"$ADFSAdminAcc`""
$ADFSAdminPW = Read-Host "Enter Password for new User `"$ADFSAdminAcc`"" `
-AsSecureString
$ADFSAdmin = New-ADUser -Name $ADFSAdminAcc `
-DisplayName $ADFSAdminAcc `
-SamAccountName $ADFSAdminAcc `
-UserPrincipalName "$ADFSAdminAcc@$env:USERDNSDOMAIN" `
-AccountPassword $ADFSAdminPW `
-Enabled $true `
-PassThru
}
Add-ADGroupMember -Identity $ADFSGroup `
-Members $ADFSAdmin.Name
# See:
# https://learn.microsoft.com/en-us/windows-server/identity/ad-fs/deployment/install-ad-fs-delegated-admin
Write-Host "Preparing domain for ADFS installation / configuration"
[string]$GuId = [Guid]::NewGuid()
$InitialPath = -join(
"CN=Microsoft,CN=Program Data,",
$objADDomain.DistinguishedName
)
$OUPath = -join(
"CN=ADFS,",
$InitialPath
)
$OU = -join(
"CN=",
$GuId,
",",
$OUPath
)
Write-Host "Checking for OU `"$OUPath`""
if($null -eq (Get-ADObject -Filter {distinguishedName -eq $OUPath})){
Write-Host "Creating OU `"ADFS`" in `"$InitialPath`""
New-ADObject -Name "ADFS" `
-Type Container `
-Path $InitialPath |
Out-Null
}
Write-Host "Adding ACL `"Generic.Read`" for `"$ADFSAdminGroup`" to `"$OUPath`""
$acl = Get-Acl -Path "AD:$OUPath"
[System.DirectoryServices.ActiveDirectorySecurityInheritance]$adSecInEnum = [System.DirectoryServices.ActiveDirectorySecurityInheritance]::All
$ace1 = New-Object System.DirectoryServices.ActiveDirectoryAccessRule $($ADFSGroup.SID),"GenericRead","Allow",$adSecInEnum
$acl.AddAccessRule($ace1)
Set-Acl -Path "AD:$OUPath" `
-AclObject $acl
Write-Host "Creating OU `"$GuId`" in `"$OUPath`""
New-ADObject -Name $GuId `
-Type Container `
-Path $OUPath
Write-Host "Adding ACLs"
Write-Host " - Generic.Read"
Write-Host " - Create.Child"
Write-Host " - Write.Owner"
Write-Host " - Delete.Tree"
Write-Host " - Write.Dacl"
Write-Host " - Write.Property"
Write-Host "for $($ADFSSvcAcc) to `"$OU`""
[System.DirectoryServices.ActiveDirectorySecurityInheritance]$adSecInEnum = [System.DirectoryServices.ActiveDirectorySecurityInheritance]::All
$ace1 = New-Object System.DirectoryServices.ActiveDirectoryAccessRule $($ADFSSvc.SID),"GenericRead","Allow",$adSecInEnum
$ace2 = New-Object System.DirectoryServices.ActiveDirectoryAccessRule $($ADFSSvc.SID),"CreateChild","Allow",$adSecInEnum
$ace3 = New-Object System.DirectoryServices.ActiveDirectoryAccessRule $($ADFSSvc.SID),"WriteOwner","Allow",$adSecInEnum
$ace4 = New-Object System.DirectoryServices.ActiveDirectoryAccessRule $($ADFSSvc.SID),"DeleteTree","Allow",$adSecInEnum
$ace5 = New-Object System.DirectoryServices.ActiveDirectoryAccessRule $($ADFSSvc.SID),"WriteDacl","Allow",$adSecInEnum
$ace6 = New-Object System.DirectoryServices.ActiveDirectoryAccessRule $($ADFSSvc.SID),"WriteProperty","Allow",$adSecInEnum
$acl = Get-Acl -Path "AD:$OU"
$acl.AddAccessRule($ace1)
$acl.AddAccessRule($ace2)
$acl.AddAccessRule($ace3)
$acl.AddAccessRule($ace4)
$acl.AddAccessRule($ace5)
$acl.AddAccessRule($ace6)
$acl.SetOwner($ADFSSvc.SID)
Set-Acl -Path "AD:$OU" `
-AclObject $acl
Write-Host "Adding ACLs"
Write-Host " - Generic.Read"
Write-Host " - Create.Child"
Write-Host " - Write.Owner"
Write-Host " - Delete.Tree"
Write-Host " - Write.Dacl"
Write-Host " - Write.Property"
Write-Host "for $($ADFSGroup) to `"$OU`""
[System.DirectoryServices.ActiveDirectorySecurityInheritance]$adSecInEnum = [System.DirectoryServices.ActiveDirectorySecurityInheritance]::All
$ace1 = New-Object System.DirectoryServices.ActiveDirectoryAccessRule $($ADFSGroup.SID),"GenericRead","Allow",$adSecInEnum
$ace2 = New-Object System.DirectoryServices.ActiveDirectoryAccessRule $($ADFSGroup.SID),"CreateChild","Allow",$adSecInEnum
$ace3 = New-Object System.DirectoryServices.ActiveDirectoryAccessRule $($ADFSGroup.SID),"WriteOwner","Allow",$adSecInEnum
$ace4 = New-Object System.DirectoryServices.ActiveDirectoryAccessRule $($ADFSGroup.SID),"DeleteTree","Allow",$adSecInEnum
$ace5 = New-Object System.DirectoryServices.ActiveDirectoryAccessRule $($ADFSGroup.SID),"WriteDacl","Allow",$adSecInEnum
$ace6 = New-Object System.DirectoryServices.ActiveDirectoryAccessRule $($ADFSGroup.SID),"WriteProperty","Allow",$adSecInEnum
$acl = Get-Acl -Path "AD:$OU"
$acl.AddAccessRule($ace1)
$acl.AddAccessRule($ace2)
$acl.AddAccessRule($ace3)
$acl.AddAccessRule($ace4)
$acl.AddAccessRule($ace5)
$acl.AddAccessRule($ace6)
$acl.SetOwner($($ADFSGroup.SID))
Set-Acl -Path "AD:$OU" `
-AclObject $acl
Write-Host "Allowing computer account `"$ADFSServerAcc`" and admin `"$ADFSAdminAcc`" to delegate / retrieve password of `"$ADFSSvcAcc`""
$ADFSServer = Get-ADComputer -Identity $ADFSServerAcc
$ADFSSvc = Get-ADServiceAccount $ADFSSvcAcc `
-Properties PrincipalsAllowedToDelegateToAccount, PrincipalsAllowedToRetrieveManagedPassword
$PATDTA = $ADFSSvc.PrincipalsAllowedToDelegateToAccount
$PATDTA += $ADFSAdmin.SID.Value, $ADFSServer.SID.Value
$PATRMP = $ADFSSvc.PrincipalsAllowedToRetrieveManagedPassword
$PATRMP += $ADFSAdmin.SID.Value, $ADFSServer.SID.Value
Set-ADServiceAccount -Identity $ADFSSvc `
-PrincipalsAllowedToDelegateToAccount $PATDTA `
-PrincipalsAllowedToRetrieveManagedPassword $PATRMP
$adminConfig = @{"DKMContainerDn"=$OU}
Write-Host "Writing Admin Config `"$($adminConfig.Keys)`" with value `"$($adminConfig.DKMContainerDn)`" to `"C:\_install\ADFSAdminConfig.txt`""
Set-Content -Path "C:\_install\ADFSAdminConfig.txt" `
-Value $OU `
-Encoding UTF8 `
-Confirm:$false `
-Force
Write-Host "Copy `"C:\_install\ADFSAdminConfig.txt`" over to new ADFS Server!"
pause
Vorbereitung, Installation und Konfiguration am neuen ADFS Server
Zur Aktivierung der Modern Auth am Exchange Server Subscription Edition wird jetzt der ADFS Server mit dem Script „Install-ADFS.ps1“ installiert. Auch hier bitte die entsprechenden Anpassungen im oberen Part des Scripts in den Zeilen 13 bis 18 vornehmen. Für die ADFS Bereitstellung verwende ich (vorerst) ebenfalls ein selbst signiertes Zertifikat für die Kommunikation mit dem Web Service. Hier jetzt der Hauptdarsteller:
# Install-ADFS.ps1
#
#
Write-Host "Installing RSAT AD tools"
Install-WindowsFeature RSAT-AD-Tools |
Out-Null
$objADDomain = Get-ADDomain -Current LocalComputer
$DNSServer = $objADDomain.PDCEmulator
# Start: Edit as needed
$ADFSName = "Binford Tools ADFS"
$ADFSFQDN = "adfs.binfordtools.net"
$OutlookUrl = "https://extern.tooltime.com/"
$AutoDUrl = "https://autodiscover.tooltime.com/"
$OWA = "https://extern.tooltime.com/owa/"
$ECP = "https://extern.tooltime.com/ecp/"
# End: Edit as needed
$exchangeServerServiceFqdns = @($OutlookUrl, $AutoDUrl)
$ADFSSvcAcc = Get-ADServiceAccount -Filter 'DnsHostName -eq $ADFSFQDN'
$ADFSgMSA = -join(
$objADDomain.NetBIOSName,
"\",
$ADFSSvcAcc.SamAccountName
)
$IPAddress = Resolve-DnsName -Name $env:COMPUTERNAME `
-Type A |
Select-Object IPAddress -ExpandProperty IPAddress
if(-not (Test-Path -Path "C:\_install" -PathType Container)){
New-Item -Path "C:\" `
-Name "_install" `
-Type Directory |
Out-Null
}
# For cert auth / device registration:
# Add certauth. / .enterpriseregistration to list of DnsNames
Write-Host "Creating ADFS service communication certificate for `"$ADFSFQDN`""
$Cert = New-SelfSignedCertificate -Subject "CN=$ADFSFQDN, O=$env:USERDNSDOMAIN, C=DE" `
-DnsName "$ADFSFQDN", $(-join($env:COMPUTERNAME, ".", $env:USERDNSDOMAIN)), $env:USERDNSDOMAIN, $env:COMPUTERNAME `
-CertStoreLocation "cert:\LocalMachine\My" `
-NotAfter (Get-Date).AddYears(10) `
-KeyAlgorithm RSA `
-KeyLength 2048 `
-KeyExportPolicy Exportable
Write-Host "Exporting certificate to C:\_install\ADFSSCCert.cer"
Export-Certificate -Cert $Cert `
-FilePath "C:\_install\ADFSSCCert.cer" |
Out-Null
Write-Host "Importing certificate from C:\_install\ADFSSCCert.cer to trusted roots"
Import-Certificate -FilePath "C:\_install\ADFSSCCert.cer" `
-CertStoreLocation Cert:\LocalMachine\Root |
Out-Null
Install-WindowsFeature -Name ADFS-Federation `
-IncludeAllSubFeature `
-IncludeManagementTools |
Out-Null
while(-not (Test-Path -Path "C:\_install\ADFSAdminConfig.txt" -PathType Leaf)){
Write-Host "Please copy ADFSAdminConfig.txt from Domain Controller where preparation was made to C:\_install on this machine!"
pause
}
$OU = Get-Content -Path "C:\_install\ADFSAdminConfig.txt"
$adminConfig = @{}
$adminConfig += @{
"dkmContainerDn" = "$OU"
}
$ADFSAdminCred = Get-Credential -Message "Enter Credentials for $env:USERNAME" `
-UserName $env:USERNAME
Install-AdfsFarm -AdminConfiguration $adminConfig `
-CertificateThumbprint $Cert.Thumbprint `
-FederationServiceName $ADFSFQDN `
-FederationServiceDisplayName $ADFSName `
-GroupServiceAccountIdentifier $ADFSgMSA `
$CertTS = (Get-ADFSCertificate Token-Signing).Certificate
Write-Host "Exporting token-signing certificate to C:\_install\ADFSTSCert.cer"
Export-Certificate -Cert $CertTS `
-FilePath "C:\_install\ADFSTSCert.cer" |
Out-Null
Write-Host "Importing token-signing certificate from C:\_install\ADFSTSCert.cer to trusted roots"
Import-Certificate -FilePath "C:\_install\ADFSTSCert.cer" `
-CertStoreLocation Cert:\LocalMachine\Root |
Out-Null
do{
$inp = Read-Host "Enable Windows authentication for SSOn? (y/n)"
}until($inp -match "^(y|n)$")
if($inp -eq "y"){
Write-Host "Setting global intranet authentication policy to `"FormsAuthentication`", `"WindowsAuthentication`""
Set-AdfsGlobalAuthenticationPolicy -PrimaryIntranetAuthenticationProvider FormsAuthentication, WindowsAuthentication
} else{
Write-Host "Setting global intranet authentication policy to `"FormsAuthentication`""
Set-AdfsGlobalAuthenticationPolicy -PrimaryIntranetAuthenticationProvider FormsAuthentication
}
Write-Host "Setting ADFS properties `"Enable keep me signed in`", `"SSo lifetime 10h`" and `"Keep me signed in lifetime 24h`""
Set-AdfsProperties -EnableKmsi $true
Set-AdfsProperties -SsoLifetime 600
Set-AdfsProperties -KmsiLifetimeMins 1440
Write-Host "Adding Windows integrated user agent `"Mozilla/5.0`""
$ADFSWIAUAs = (Get-AdfsProperties).WIASupportedUserAgents
$ADFSWIAUAs += "Mozilla/5.0"
Set-AdfsProperties -WIASupportedUserAgents $ADFSWIAUAs
Write-Host "Creating ADFS application group `"Outlook"
New-AdfsApplicationGroup -Name "Outlook" `
-ApplicationGroupIdentifier "Outlook" `
-Disabled:$false
Write-Host "Adding ADFS scope descriptions `"EAS scope`", `"EWS scope`" and `"Offline Access`""
Add-AdfsScopeDescription -Name "EAS.AccessAsUser.All" `
-Description "EAS scope"
Add-AdfsScopeDescription -Name "EWS.AccessAsUser.All" `
-Description "EWS scope"
Add-AdfsScopeDescription -Name "offline_access" `
-Description "Offline Access"
Write-Host "Adding ADFS native client application `"Outlook - Native application`""
Add-AdfsNativeClientApplication -Name "Outlook - Native application" `
-ApplicationGroupIdentifier "Outlook" `
-Identifier "d3590ed6-52b3-4102-aeff-aad2292ab01c" `
-RedirectUri @("ms-appx-web://Microsoft.AAD.BrokerPlugin/d3590ed6-52b3-4102-aeff-aad2292ab01c","msauth.com.microsoft.Outlook://auth","urn:ietf:wg:oauth:2.0:oob")
Write-Host "Adding ADFS native client application `"iOS and macOS - Native mail application`""
Add-AdfsNativeClientApplication -Name "iOS and macOS - Native mail application" `
-ApplicationGroupIdentifier "Outlook" `
-Identifier "f8d98a96-0999-43f5-8af3-69971c7bb423" `
-RedirectUri @("com.apple.mobilemail://oauth-redirect","com.apple.preferences.internetaccounts://oauth-redirect/","com.apple.Preferences://oauth-redirect/")
$issuanceTransformRules = @"
@RuleName = "ActiveDirectoryUserSID"
c:[Type == "http://schemas.microsoft.com/ws/2008/06/identity/claims/primarysid"]
=> issue(claim = c);
@RuleName = "ActiveDirectoryUPN"
c:[Type == "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn"]
=> issue(claim = c);
@RuleName = "AppIDACR"
=> issue(Type = "appidacr", Value = "2");
@RuleName = "SCP"
=> issue(Type = "scp", Value = "user_impersonation");
@RuleName = "SCPEAS"
=> issue(Type = "scp", Value = "EAS.AccessAsUser.All");
@RuleName = "SCPEWS"
=> issue(Type = "scp", Value = "EWS.AccessAsUser.All");
@RuleName = "offlineaccess"
=> issue(Type = "scp", Value = "offline_access");
"@
foreach ($fqdn in $exchangeServerServiceFqdns) {
Write-Host "Adding ADFS Web Api for `"$fqdn`""
Add-AdfsWebApiApplication -Name "Outlook - Web API ($((New-Guid).ToString("N")))" `
-ApplicationGroupIdentifier "Outlook" `
-Identifier $fqdn `
-IssuanceTransformRules $issuanceTransformRules `
-AccessControlPolicyName "Permit Everyone"
}
$clientRoleIdentifier = @("f8d98a96-0999-43f5-8af3-69971c7bb423", "d3590ed6-52b3-4102-aeff-aad2292ab01c")
Get-AdfsWebApiApplication -ApplicationGroupIdentifier "Outlook" |
ForEach-Object{
[string]$serverRoleIdentifier = $_.Identifier
foreach($id in $clientRoleIdentifier){
Write-Host "Granting ADFS application for $serverRoleIdentifier permission to $id"
Grant-AdfsApplicationPermission -ClientRoleIdentifier $id `
-ServerRoleIdentifier $serverRoleIdentifier `
-ScopeNames "winhello_cert","email","profile","vpn_cert","logon_cert","user_impersonation","allatclaims","offline_access","EAS.AccessAsUser.All","EWS.AccessAsUser.All","openid","aza"
}
}
Write-Host "Adding ADFS relaying party trust for Outlook on the Web"
Add-AdfsRelyingPartyTrust -Name "Outlook on the web" `
-Notes "This is a trust for $OWA" `
-Identifier $OWA `
-WSFedEndpoint $OWA `
-IssuanceAuthorizationRules '@RuleTemplate = "AllowAllAuthzRule" => issue(Type = "http://schemas.microsoft.com/authorization/claims/permit", Value = "true");' `
-IssueOAuthRefreshTokensTo NoDevice
Set-AdfsRelyingPartyTrust -TargetName "Outlook on the web" `
-IssuanceTransformRules '@RuleName = "ActiveDirectoryUserSID" c:[Type == "http://schemas.microsoft.com/ws/2008/06/identity/claims/windowsaccountname", Issuer == "AD AUTHORITY"] => issue(store = "Active Directory", types = ("http://schemas.microsoft.com/ws/2008/06/identity/claims/primarysid"), query = ";objectSID;{0}", param = c.Value); @RuleName = "ActiveDirectoryUPN" c:[Type == "http://schemas.microsoft.com/ws/2008/06/identity/claims/windowsaccountname", Issuer == "AD AUTHORITY"] => issue(store = "Active Directory", types = ("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn"), query = ";userPrincipalName;{0}", param = c.Value);'
Write-Host "Adding ADFS relaying party trust for Exchange control panel"
Add-AdfsRelyingPartyTrust -Name EAC -Notes "This is a trust for $ECP" `
-Identifier $ECP `
-WSFedEndpoint $ECP `
-IssuanceAuthorizationRules '@RuleTemplate = "AllowAllAuthzRule" => issue(Type = "http://schemas.microsoft.com/authorization/claims/permit", Value = "true");' `
-IssueOAuthRefreshTokensTo NoDevice
Set-AdfsRelyingPartyTrust -TargetName EAC `
-IssuanceTransformRules '@RuleName = "ActiveDirectoryUserSID" c:[Type == "http://schemas.microsoft.com/ws/2008/06/identity/claims/windowsaccountname", Issuer == "AD AUTHORITY"] => issue(store = "Active Directory", types = ("http://schemas.microsoft.com/ws/2008/06/identity/claims/primarysid"), query = ";objectSID;{0}", param = c.Value); @RuleName = "ActiveDirectoryUPN" c:[Type == "http://schemas.microsoft.com/ws/2008/06/identity/claims/windowsaccountname", Issuer == "AD AUTHORITY"] => issue(store = "Active Directory", types = ("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn"), query = ";userPrincipalName;{0}", param = c.Value);'
Write-Host "Copy both certificates (ADFSSCCert.cer / ADFSTSCert.cer) from C:\_install to Domain Controller and run script `"Import-ADFSCertificates.ps1`""
Write-Host "Server will reboot after pressing <ENTER>"
pause
Restart-Computer
Zwischenstopp und kurz zurück auf den / einen Domain Controller
Da ich mich für den Artikel entschieden habe, den ADFS Server ohne Domain Admin zu installieren, kann ich mit meinem Standard User mit lokalen Adminrechten auf dem ADFS Server keine GPOs bearbeiten. Daher geht es mit den beiden Zertifikaten (ADFSSCCert.cer für die Service Communication; ADFSTSCert.cer fürs Token Signing) zurück auf einen Domain Controller und dem Script „Import-ADFSCertificates.ps1“:
# Import-ADFSCertificates.ps1
#
#
$CertGPO = "C_Distribute Root certificates"
$CertTPs = @()
while(($Certs = Get-ChildItem -Path "C:\_install" -Filter ADFS*Cert.cer).Count -ne 2){
Write-Host "Copy both certificates (ADFSSCCert.cer / ADFSTSCert.cer) from ADFS Server C:\_install to Domain Controller and press <ENTER>"
pause
}
$objCertGPO = Get-GPO -All |
Where-Object { $_.DisplayName -eq $CertGPO }
if($null -eq $objCertGPO){
$objCertGPO = New-GPO -Name $CertGPO
$objCertGPO.GpoStatus = "UserSettingsDisabled"
Write-Host "Linking GPO `"$CertGPO`" to `"$($objADDomain.DistinguishedName)`""
New-GPLink -Guid $objCertGPO.Id `
-Target $objADDomain.DistinguishedName `
-LinkEnabled Yes |
Out-Null
}
foreach($Cert in $Certs){
Write-Host "Importing certificate `"$($Cert.Name)`""
$tmpCert = Import-Certificate -FilePath $Cert.FullName `
-CertStoreLocation Cert:\LocalMachine\Root
$certRegPath = "HKLM:\SOFTWARE\Microsoft\SystemCertificates\Root\Certificates\{0}" `
-f $tmpCert.Thumbprint
$certBlob = Get-ItemProperty -Path $certRegPath `
-Name "Blob" |
Select-Object Blob `
-Expand Blob
Write-Host "Adding certificate `"$($Cert.Name)`" with thumbprint `"$($tmpCert.Thumbprint)`" to GPO `"$CertGPO`""
$certPolRegPath = "HKLM\SOFTWARE\Policies\Microsoft\SystemCertificates\Root\Certificates\{0}" `
-f $tmpCert.Thumbprint
Set-GPRegistryValue -Name $CertGPO `
-Key $certPolRegPath `
-ValueName "Blob" `
-Type Binary `
-Value $certBlob |
Out-Null
}
Zuletzt aktivieren wir die Modern Auth am Exchange SE
Mit dem letzten Script „Configure-ExADFS-ModernAuth.ps1“ aktivieren wir die Modern Auth am Exchange Server SE. Das erstellte Authentication Profile weisen wir vorerst nur einem (oder ein paar) Test Usern zu. Auch im Script „Configure-ExADFS-ModernAuth.ps1“ bitte die Zeilen 8 bis 12 an die eigene Umgebung anpassen.
# Configure-ExADFS-ModernAuth.ps1
#
#
$objADDomain = Get-ADDomain -Current LocalComputer
# Start: Edit as needed
$ADFSName = "Binford Tools ADFS"
$ADFSFQDN = "adfs.binfordtools.net"
$OWA = "https://extern.tooltime.com/owa/"
$ECP = "https://extern.tooltime.com/ecp/"
$Users = @("Exch.Admin", "Test.User1", "Test.User2")
# End: Edit as needed
$ExchSRV = Get-ADComputer -Identity $env:COMPUTERNAME `
-Properties DnsHostName
$ExchConUri = -join("http://", `
$ExchSRV.DNSHostName , `
"/PowerShell" `
)
Write-Host "Running gpupdate to fetch new GPO with certificates"
Invoke-Expression -Command "C:\Windows\system32\gpupdate.exe /force"
Write-Host "Connecting to Exchange `"$env:COMPUTERNAME` ($ExchConUri)"
$ExchSession = New-PSSession -ConfigurationName Microsoft.Exchange `
-ConnectionUri $ExchConUri
Import-PSSession -Session $ExchSession -DisableNameChecking `
-AllowClobber |
Out-Null
Write-Host "Creating (temporary) authentication policy to block modern auth per default"
New-AuthenticationPolicy "Block Modern auth" `
-BlockModernAuthWebServices `
-BlockModernAuthActiveSync `
-BlockModernAuthAutodiscover `
-BlockModernAuthImap `
-BlockModernAuthMapi `
-BlockModernAuthOfflineAddressBook `
-BlockModernAuthPop `
-BlockModernAuthRpc
Write-Host "Setting `"Block Modern auth`" authentication policy as default"
Set-OrganizationConfig -DefaultAuthenticationPolicy "Block Modern auth"
Write-Host "Creating `"Allow Modern auth`" per user authentication policy"
New-AuthenticationPolicy "Allow Modern auth" `
-BlockLegacyAuthActiveSync `
-BlockLegacyAuthOfflineAddressBook `
-BlockLegacyAuthRpc `
-BlockLegacyAuthWebServices `
-BlockLegacyAuthAutodiscover `
-BlockLegacyAuthMapi
Write-Host "Creating new auth server"
New-AuthServer -Type ADFS `
-Name $ADFSName `
-AuthMetadataUrl "https://$ADFSFQDN/FederationMetadata/2007-06/FederationMetadata.xml" |
Out-Null
Write-Host "Setting new auth server as default authorization endpoint"
Set-AuthServer -Identity $ADFSName `
-IsDefaultAuthorizationEndpoint $true
Write-Host "Fetching ADFS signing certificate from trusted roots"
$ADFSSCert = Get-ChildItem -Path Cert:\LocalMachine\Root |
Where-Object { $_.Subject -match "^CN=ADFS Signing - $ADFSFQDN$" }
Write-Host "Updating organization config for ADFS use and enabling OAuth2"
Set-OrganizationConfig -AdfsIssuer "https://$ADFSFQDN/adfs/ls/" `
-AdfsAudienceUris $OWA, $ECP `
-AdfsSignCertificateThumbprint $ADFSSCert.Thumbprint
Set-OrganizationConfig -OAuth2ClientProfileEnabled $true
Write-Host "Setting Active Sync authentication methods to support modern auth"
Get-ActiveSyncVirtualDirectory |
Set-ActiveSyncVirtualDirectory -InternalAuthenticationMethods Ntlm, OAuth, Negotiate `
-ExternalAuthenticationMethods Ntlm, OAuth, Negotiate
Write-Host "Enabling ADFS authentication for Exchange control panel (ECP)"
Get-EcpVirtualDirectory |
Set-EcpVirtualDirectory -AdfsAuthentication $true `
-BasicAuthentication $false `
-DigestAuthentication $false `
-FormsAuthentication $false `
-OAuthAuthentication $false `
-WindowsAuthentication $false
Write-Host "Enabling ADFS authentication for Outlook Web App (OWA)"
Get-OwaVirtualDirectory |
Set-OwaVirtualDirectory -AdfsAuthentication $true `
-BasicAuthentication $false `
-DigestAuthentication $false `
-FormsAuthentication $false `
-OAuthAuthentication $false `
-WindowsAuthentication $false
foreach($u in $Users){
Write-Host "Assigning authentication policy `"Allow Modern auth`" to $u"
Set-User -Identity $u `
-AuthenticationPolicy "Allow Modern auth"
}
Weil es so schön ist: Bunte Bilder zum Abschluss
Ablauf bei deaktiviertem SSOn / Windows Authentifizierung:
- Aufruf der Outlook Web App (OWA) oder des Exchange Control Panels (ECP) im Browser
- Umleitung zum ADFS
- Anmeldung
- Umleitung zu OWA / zum ECP



- Beim Starten von Outlook (mit gesetzter Policy: GPS: Automatically configure profile based on Active Directory Primary SMTP address):


Ablauf bei aktiviertem SSOn / Windows Authentifizierung:
- Aufruf der Outlook Web App (OWA) oder des Exchange Control Panels (ECP) im Browser
- Uuund… Schwupps, angemeldet 🙂


- Beim Starten von Outlook (mit gesetzter Policy: GPS: Automatically configure profile based on Active Directory Primary SMTP address) kann man sich zumindest das Tippen des Passworts sparen und den „Sign in as current user“ wählen: 😉


Und warum der ganze Spaß? Ein Ausblick:
Nunja 🤷♂️, eigentlich ganz einfach. Dank dem ADFS sollte sich in die gesamte Authentifizierung auch eine Zwei-Faktor-Authentisierung bzw. eine Multi-Faktor-Authentisierung in alle (bzw. die wichtigsten) Exchange Server Protokolle (OWA, ECP, EAS, MAPI/HTTP und OAB) bspw. von ESET (Einfach zu verwaltende und sichere Multi-Faktor-Authentifizierung | ESET | ESET) oder auch Duo (Complete Identity Security & MFA Solutions | Duo Security) implementieren lassen. Dazu aber mehr im vermutlich nächsten oder übernächsten Artikel. 😅
Schreibe einen Kommentar