Hybrid Modern Authentication Checker

Scenario: If you are having issues with Hybrid Modern authentication in your Exchange On-Premises environment, feel free to use some of the logic below in the script.

Script: If Hybrid Modern authentication is already in place, feel free to start with the following:
1. Run the Test-OAUTHConnectivity command (Step 7)
2. Run the Hybrid Config Wizard should put the pieces in place necessary.
3. We had to perform step 3 due to an HMA interactive signin loop.



#Hybrid Modern Authentication Checker
#Steps expanded based on this article: https://docs.microsoft.com/en-us/exchange/configure-oauth-authentication-between-exchange-and-exchange-online-organizations-exchange-2013-help#step-5-register-all-hostname-authorities-for-your-internal-and-external-on-premises-exchange-http-endpoints-with-azure-active-directory

#1. Connect to Exchange On-Premises
        $n = "ACS","EvoSts"
        Get-AuthServer  | Where name -in $n
        #If both $severs exist,  its good!

#2. Make sure the Partner Application is Enabled
        Get-PartnerApplication |  ?{$_.ApplicationIdentifier -eq "00000002-0000-0ff1-ce00-000000000000" -and $_.Realm -eq ""}

#3. Identify the Current Certificate being used in the Get-AuthConfiguration for ServiceName 00000002-0000-0ff1-ce00-000000000000

        #Get Thumbprint
                $thumbprint = (Get-AuthConfig).CurrentCertificateThumbprint
        
        #Check Cert Expiration (NotAfter)
                Get-ExchangeCertificate -Thumbprint $thumbprint | Select Thumbprint, Services, Subject,NotAfter

        #Check to see what certificate is being used in Microsoft Online, Verify with the StartDate/EndDate
                Connect-msolservice 
                Get-MsolServicePrincipalCredential -ServicePrincipalName "00000002-0000-0ff1-ce00-000000000000" -ReturnKeyValues $true |Select Type, KeyID, Startdate,Enddate

        #If we are receiving a Modern Auth Loop, Run the following to export and re-import the certificate:
            #Export from On-Premises
                $thumbprint = (Get-AuthConfig).CurrentCertificateThumbprint
                if((test-path $env:SYSTEMDRIVE\OAuthConfig) -eq $false)
                {
                    md $env:SYSTEMDRIVE\OAuthConfig
                }
                cd $env:SYSTEMDRIVE\OAuthConfig
                $oAuthCert = (dir Cert:\LocalMachine\My) | where {$_.Thumbprint -match $thumbprint}
                $certType = [System.Security.Cryptography.X509Certificates.X509ContentType]::Cert
                $certBytes = $oAuthCert.Export($certType)
                $CertFile = "$env:SYSTEMDRIVE\OAuthConfig\OAuthCert.cer"
                [System.IO.File]::WriteAllBytes($CertFile, $certBytes)

            #Import into Microsoft Online 
                Connect-MsolService
                $CertFile = "$env:SYSTEMDRIVE\OAuthConfig\OAuthCert.cer"
                $objFSO = New-Object -ComObject Scripting.FileSystemObject
                $CertFile = $objFSO.GetAbsolutePathName($CertFile)
                $cer = New-Object System.Security.Cryptography.X509Certificates.X509Certificate
                $cer.Import($CertFile)
                $binCert = $cer.GetRawCertData()
                $credValue = [System.Convert]::ToBase64String($binCert)
                $ServiceName = "00000002-0000-0ff1-ce00-000000000000"
                $p = Get-MsolServicePrincipal -ServicePrincipalName $ServiceName
                New-MsolServicePrincipalCredential -AppPrincipalId $p.AppPrincipalId -Type asymmetric -Usage Verify -Value $credValue

            #Verify the new certificate is there:
                Connect-MsolService
                Get-MsolServicePrincipalCredential -ServicePrincipalName "00000002-0000-0ff1-ce00-000000000000" -ReturnKeyValues $true


#4. SPN Check:
    #Verify the namespaces used in Exchange On-Prmeises are all present and registered as SPNs in Microsoft Online 
        Get-MapiVirtualDirectory | FL server,*url*
        Get-WebServicesVirtualDirectory | FL server,*url*
        Get-ClientAccessServer | fl Name, AutodiscoverServiceInternalUri
        Get-OABVirtualDirectory | FL server,*url*
        Get-AutodiscoverVirtualDirectory | FL server,*url*
        Get-OutlookAnywhere | FL server,*url*

    #Verify the SPNs (urls) are listed in Microsoft for 00000002-0000-0ff1-ce00-000000000000
        Get-MsolServicePrincipal -AppPrincipalId 00000002-0000-0ff1-ce00-000000000000 | select -ExpandProperty ServicePrincipalNames

    #If you need to add a SPN because a new namespace is being used, or if a namespace is missing:
        $ServiceName = "00000002-0000-0ff1-ce00-000000000000";
        $x = Get-MsolServicePrincipal -AppPrincipalId $ServiceName;
        $x.ServicePrincipalnames.Add("https://mail.contoso.com/");
        $x.ServicePrincipalnames.Add("https://autodiscover.contoso.com/");
        Set-MSOLServicePrincipal -AppPrincipalId $ServiceName -ServicePrincipalNames $x.ServicePrincipalNames;


#5. Verify the IntraOrganizationConnector in Exchange On-Premises
            Get-IntraOrganizationConnector

        #If you need to add it:
            $ServiceDomain = Get-AcceptedDomain | where {$_.DomainName -like "*.mail.onmicrosoft.com"} | select -ExpandProperty Name
            New-IntraOrganizationConnector -name ExchangeHybridOnPremisesToOnline -DiscoveryEndpoint https://outlook.office365.com/autodiscover/autodiscover.svc -TargetAddressDomains $ServiceDomain



#6. Verify the IntraOrganizationConnector in Exchange Online
            get-intraorganizationconnector

            #If you need to add it
            New-IntraOrganizationConnector -name ExchangeHybridOnlineToOnPremises -DiscoveryEndpoint <your on-premises Autodiscover endpoint> -TargetAddressDomains <your on-premises SMTP domain>


#7. Test Oauth Connectivity

        #From Exchange Online PowerShell
            test-OAuthConnectivity -Service EWS -TargetUri https://<OnPremNamespace>/metadata/json/1 -Mailbox <ExOnline MBX UPN> -Verbose
        
        #From Exchange On-Premises PowerShell
            test-OAuthConnectivity -Service EWS -TargetUri https://outlook.office365.com/ews/exchange.asmx -Mailbox  -Verbose


Hybrid Modern Authentication Authentication Loop

Scenario: When authenticating to an Exchange On-Premises mailbox with using the interactive login presented by Hybrid Modern Authentication, you get stuck in an endless loop of interactive login attempts:

1. You successfully satisfy the interactive logon
2. The interactive login window disappears as it normally should after a successful login
3. But immediately the interactive windows pops back up for you to do it again.

Solution: In our case, we had to replace the Authentication Certificate that was being used in the MSOLServicePrincipalCredential for Exchange Online. However, lets walk through some of the troubleshooting than into the fix:

1. TEST OAUTH Connectivity
I tested OAUTH Connectivity between Exchange Online and Exchange On-Premises, running these commands:

#From Exchange Online PowerShell
test-OAuthConnectivity -Service EWS -TargetUri https://<OnPremises Exchange Namespace>/metadata/json/1 -Mailbox <ExOnline MBX UPN> -Verbose

#From Exchange On-Premises PowerShell
test-OAuthConnectivity -Service EWS -TargetUri https://outlook.office365.com/ews/exchange.asmx -Mailbox <ExOnline MBX UPN> -Verbose

Both resulted in ResultType: Error with the details of the Error being:

System.net.webexception: The remote server returned an error: (401) Unauthorized. ……. Reason=”The token has an invalid signature.”;error_category=”invalid_signature

2. Hmm, seems like our Hybrid Modern Auth is broke — Bad tokens are being generated. So its time to start digging into the Configure OAUTH authentication between Exchange and Exchange Online Organizations

Now, Hybrid Modern Authentication was already enabled and working at some point in the past in this environment– how long ago? not really sure. So I know most of these components are already there and enabled. To jump straight to the solution that fixed us, it was Step 3 and Step 4,

Step 3: Export the on-premises authorization certificate

$thumbprint = (Get-AuthConfig).CurrentCertificateThumbprint
if((test-path $env:SYSTEMDRIVE\OAuthConfig) -eq $false)
{
   md $env:SYSTEMDRIVE\OAuthConfig
}
cd $env:SYSTEMDRIVE\OAuthConfig
$oAuthCert = (dir Cert:\LocalMachine\My) | where {$_.Thumbprint -match $thumbprint}
$certType = [System.Security.Cryptography.X509Certificates.X509ContentType]::Cert
$certBytes = $oAuthCert.Export($certType)
$CertFile = "$env:SYSTEMDRIVE\OAuthConfig\OAuthCert.cer"
[System.IO.File]::WriteAllBytes($CertFile, $certBytes)

Step 4: Upload the on-premises authorization certificate to Azure Active Directory Access Control Service (ACS)

Connect-MsolService
$CertFile = "$env:SYSTEMDRIVE\OAuthConfig\OAuthCert.cer"
$objFSO = New-Object -ComObject Scripting.FileSystemObject
$CertFile = $objFSO.GetAbsolutePathName($CertFile)
$cer = New-Object System.Security.Cryptography.X509Certificates.X509Certificate
$cer.Import($CertFile)
$binCert = $cer.GetRawCertData()
$credValue = [System.Convert]::ToBase64String($binCert)
$ServiceName = "00000002-0000-0ff1-ce00-000000000000"
$p = Get-MsolServicePrincipal -ServicePrincipalName $ServiceName
New-MsolServicePrincipalCredential -AppPrincipalId $p.AppPrincipalId -Type asymmetric -Usage Verify -Value $credValue

After running this command, I immediately tested with my email client and Poof — no more interactive logon loop. Test-OAUTHConnectivity was also coming back a success. One more command that I found helpful was verifying the MSOLServicePrincipalCredential:

Get-MsolServicePrincipalCredential -ServicePrincipalName “00000002-0000-0ff1-ce00-000000000000” -ReturnKeyValues $true | Select Type, KeyID, StartDate,EndDate

After I uploaded, and actually did it twice to see if there would be any negative impact (there was not), I saw the new certificate listed. You can easily identify the cert by the StartDate/EndDate of it.

Uploading the new cert fixed it!

AADSTS500011: The resource principal named was not found in the tenant

Scenario: If your receive the following error when using Hybrid Modern Authentication for Exchange OnPremises, run the following scriptlet below. Our problem was a misconfigured URL set on the get-clientaccessserver where it was pointing to a Servername, and not our shared namespace.

Scriptlet:

 Get-MapiVirtualDirectory | FL server,*url*
 Get-WebServicesVirtualDirectory | FL server,*url*
 Get-ClientAccessServer | fl Name, AutodiscoverServiceInternalUri
 Get-OABVirtualDirectory | FL server,*url*
 Get-AutodiscoverVirtualDirectory | FL server,*url*
 Get-OutlookAnywhere | FL server,*url*

Configure your Exchange Online PowerShell Script to leverage OAUTH/Modern Authentication and Authenticate Silently

Scenario: You need to connect to Exchange Online PowerShell via a script that will silently authenticate using Modern Authentication/OAuth.

Microsoft is deprecating Legacy/Basic Authentication when connecting to Exchange Online. Your existing scripts that leverages a username/password to authenticate silently, either by hardcoding a username or password into the script OR using encrypted keys that PowerShell calls in, will break when legacy authentication is officially disabled. You will need to convert the logic us to now start connecting to Exchange Online via Modern Authentication/Oauth.

If you haven’t yet asked ‘How to do we do that’? I am glad I asked for you….

1. You need the latest release of the ExchangeOnlineManagement (Connect-ExchangeOnline) module installed in PowerShell : Install-Module -name ExchangeOnlineManagement.

2. Setup App-only application in Azure.

2a. Register a new application object in Azure Active Directory

2b. Provide the Azure applications the following API permission: Exchange.ManageAsApp

2c. Create a self-signed cert in PowerShell that will be used to authenticate to the Azure App.
# Create certificate
$mycert = New-SelfSignedCertificate -DnsName “whatever.com” -CertStoreLocation “cert:\LocalMachine\My” -NotAfter (Get-Date).AddYears(3) -KeySpec KeyExchange

#Export certificate to .pfx file
$mycert | Export-PfxCertificate -FilePath c:\temp\mycert.pfx -Password $(ConvertTo-SecureString -String “PasswordForCert!” -Force -AsPlainText)

#Export certificate to .cer file
$mycert | Export-Certificate -FilePath c:\temp\mycert.cer


2d. Upload the Certificate in the Certificates & Secrets of the Azure App.

3. Assign the Exchange Administrator role (via Azure Roles or MSOL Roles) to the new Registered App.

4. Now that the app is configured with Exchange Permissions, Exchange Access, and the Certificate uploaded, connect to Exchange Online using the pfx Cert from PowerShell:

Connect-ExchangeOnline -CertificateFilePath “C:\temp\mycert.pfx” -CertificatePassword (ConvertTo-SecureString -String “PasswordForCert!” -AsPlainText -Force) -AppID “<AppID of your new registered app>” -Organization “<your tenant organization name>”