Those who have worked with Exchange Online PowerShell for a while will know how much a challenge it was to move from basic to modern authentication with MFA for unattended scripts that are scheduled to run non-interactively. The introduction and enforcement of modern authentication and MFA meant attempting to run unattended scripts would no longer work because it would require an administrator to interactively enter credentials and authenticate with the 2nd authentication. As such, many administrators continued to use basic authentication or modern authentication with accounts without MFA enforced to work around the issue, which leads to vulnerability issues. Microsoft originally had plans to disable basic authentication in 2020 but delayed it indefinitely.
Given the challenge as described above, I was extremely excited when Microsoft released the EXO V2 2.0.3 module for public preview in July 2020 where it introduced certificate based authentication. This meant it was now possible to connect to Exchange Online with unattended scripts that no longer passed a username and password, and therefore eliminating the issue where MFA is required upon authentication. I haven’t been working with Exchange Online for sometime due to my new role but was asked by an ex-colleague about setting up unattended scripts so I took the opportunity to capture the process while demonstrating the setup, which I will now use for this blog.
An organization wants to use a PowerShell script to export Office 365 audit logs of users’ events from Exchange Online, SharePoint Online, OneDrive for Business, Azure Active Directory, Microsoft Teams, Power BI, and other Microsoft 365 services with the Audit Log search feature in the following two Microsoft 365 consoles:
Office 365 Security & Compliance
Microsoft 365 compliance
The log will then get emailed for review at the end of the month with a scheduled task.
As this will be an automated process that runs at the end of each month, attempting to setup a PowerShell script that requires using Connect-ExchangeOnline with basic or modern authentication means a username and password will be used with an account without MFA.
With the release of EXO V2 2.0.3, unattended scripts (automation) scenarios can now authenticate using Azure AD applications and self-signed certificates.
How does it work?
The EXO V2 module uses the Active Directory Authentication Library to fetch an app-only token using the application Id, tenant Id (organization), and certificate thumbprint. The application object provisioned inside Azure AD has a Directory Role assigned to it, which is returned in the access token. Exchange Online configures the session RBAC using the directory role information that's available in the token.
To accomplish the task above, we will require the following components:
- EXO V2 2.0.3 or higher module
- PowerShell Version 7 and higher
- Self-signed certificate
- PowerShell Script using the Search-UnifiedAuditLog (https://docs.microsoft.com/en-us/powershell/module/exchange/search-unifiedauditlog?view=exchange-ps)
This post will focus on setting up the components required for certificate based authentication but for those who are interested in #4, I’ve written another blog post and will provide the following link to that post:
Script to export audit logs for the current month from Office 365 using Search-UnifiedAuditLog
Official Microsoft Documentation
As always, I’d like to provide the official Microsoft documentation and other useful reference documents here:
App-only authentication for unattended scripts in the EXO V2 module
Modern Auth and Unattended Scripts in Exchange Online PowerShell V2
About the Exchange Online PowerShell V2 module
Basic Authentication and Exchange Online – July Update
UPDATE: Exchange Online deprecating Basic Authentication (Basic Auth)
Step #1 – Required Module and PowerShell
Begin by obtaining the required PowerShell and EXO V2 modules as using the incorrect version of say, PowerShell, will cause the certificate based authentication to fail. Download and install the latest versions of the following:
ExchangeOnlineManagement 2.0.5 (the latest at the time of this writing)
v7.1.3 Release of PowerShell (the latest at the time of this writing)
While not a requirement, I highly recommend using Visual Studio Code and the PowerShell extension to write and test PowerShell scripts as the Windows built-in ISE only supports version 5:
Visual Studio Code
Using Visual Studio Code for PowerShell Development
Step #2 – Register an application in Azure AD
In order to authenticate with the EXO V2 module’s Connect-ExchangeOnline with a certificate, you must register the an application in Azure AD, which will represent the unattended script.
Begin by logging into https://portal.azure.com, navigate to Azure Active Directory > App registrations:
Click on the New Registration button to register a new app that will represent the identity of the unattended script:
Provide a name for the application and select the appropriate account types option. For the purpose of this demonstration, we’re limiting to the single tenant so the following is selected:
Accounts in this organizational directory only (<YourOrganizationName> only - Single tenant)
The Redirect URI (optional) field is not required for what we’re trying to accomplish so leave Web as the selection and the URI empty.
The newly created registration of the application should now be displayed:
With the application created, we’ll need to assign the appropriate permissions to the application that will represent our unattended script. Proceed to navigate into the configuration of the registered application:
There are two ways to configure the appropriate permissions:
Option #1 – Use the Manifest configuration
This is the easiest as you simply edit the manifest properties, which will configure the permissions and remove the default Microsoft Graph > User.Read permissions as shown here in the API permissions:
Navigate to the Manifest configuration and locate requiredResourceAccess entry around line 44:
Click Save to apply the changes:
Navigate to API permissions should now display the Exchange.ManageAsApp permissions configured:
Proceed to grant admin consent for the configured permission:
Option #2 – Use the API permissions configuration
The second option is to use the API permissions to manually add the preproperate permissions for the app:
Search for Office 365 Exchange Online:
Select Application permissions:
Under Exchange (1), select Exchange.ManageAsApp:
Grant admin consent for the Exchange.ManageAsApp permissions that was just assigned:
Remove the Microsoft Graph permissions:
Step #3 – Generate a self-signed certificate for the application that will be authenticating
The next step is to generate a self-signed X.509 certificate that the application will use to authenticate against Azure AD to request the app-only access token. It is also possible to use an internal or public PKI infrastructure for the certificate but this demonstration will use a self-signed certificate that can be generated locally on a Windows Server with PowerShell version 7.
Note that next Generation (CNG) certificates are not supported for app-only authentication with Exchange. CNG certificates are created by default in modern Windows versions. You must use a certificate from a CSP key provider.
To generate a self-signed certificate for authentication, log onto any Windows Server or desktop with PowerShell version 7 or newer and execute the following:
# Create certificate
$mycert = New-SelfSignedCertificate -DnsName "contoso.org" -CertStoreLocation "cert:\LocalMachine\My" -NotAfter (Get-Date).AddYears(1) -KeySpec KeyExchange
A certificate with the private key will be created in the local computer store:
The certificate with the private key located on the computer will be used to authenticate the identity of an unattended script. A corresponding certificate with the public key (without the private key) will need to be attached to the Azure AD application. Execute the following cmdlet to create a .cer file with the public key:
# Export certificate to .cer file
$mycert | Export-Certificate -FilePath mycert.cer
If the intention is to execute Connect-ExchangeOnline from the server where this self-signed certificate is generated then we can simply reference it with its thumbprint when we authenticate. If the script will reside on another server that may not be Windows or have the certificate imported into the local computer store then it is possible to export the certificate with the private key to a .pfx file, which can then be used to authenticate. Scenarios such as having an App Service that needs to authenticate against Azure AD can have the .pfx file stored in the Azure Key Vault and retrieved during the authentication process. Use the following cmdlet to export the certificate to a pfx file:
# Export certificate to .pfx file
$mycert | Export-PfxCertificate -FilePath mycert.pfx -Password $(ConvertTo-SecureString -String "P@ssw0Rd1234" -AsPlainText -Force)
Step #4 – Attach the self-signed certificate to the Azure AD application
The next step is to upload the certificate with the public key (without the private key) to the Azure AD application. Proceed to navigate to the App registrations configuration in the Azure portal and click into the application:
Navigate to Certificates & secrets and click on the Upload certificate button:
Upload the .cer export of the certificate:
The uploaded certificate will be displayed:
Step #5 – Assign the required Azure AD roles to the application
The last step for the configuration is the RBAC roles with the required permissions for the registered application so it is able to execute the required cmdlets with the EXO V2 module because authenticating with the certificate will not be in the context of a user. Not all of the Azure AD roles are currently supported but the following ones are:
- Global administrator
- Compliance administrator
- Security reader
- Security administrator
- Helpdesk administrator
- Exchange administrator
- Global Reader
Begin by navigating to Azure Active Directory > Roles and administrators, search for the Exchange administrator role and open the properties:
Click on Add assignments:
Search for the app registration we created earlier to grant the service principal permissions:
Note how the registered app is now assigned Exchange administrator permissions:
Step #6 – Authenticate against Azure AD with Connect-ExchangeOnline using the certificate
We can now use the certificate to authenticate against Azure AD with the Connect-ExchangeOnline cmdlet. Proceed to obtain the Application (client) ID from the Overview properties of the registered application:
And the thumbprint of the certificate from the Certificates & secrets of the registered application or the certificate on the Windows operating system’s local computer store:
With the variable properties above, the following cmdlet can be used to authenticate.
Authenticating with a certificate stored on the Windows server or desktop’s Local Computer > Personal Certificates store
Ensure that the certificate is located in the appropriate store:
Connect-ExchangeOnline -CertificateThumbPrint "3D057B3299A75B824F326F1A8A64262F12C60958" -AppID "b6925809-be8c-441b-8915-1bbcc2f2b6fc" -Organization "contoso.onmicrosoft.com"
Authenticating with an exported PFX file
Alternatively, you can also use an exported PFX to connect via the following cmdlet:
Connect-ExchangeOnline -CertificateFilePath "C:\scripts\mycert.pfx" -CertificatePassword (ConvertTo-SecureString -String "<MyPassword>" -AsPlainText -Force) -AppID "36ee4c6c-0812-40a2-b820-b22ebd02bce3" -Organization "contoso.onmicrosoft.com"
Authenticating with a certificate object (e.g. retrieved from Azure Key Vault)
The last method to provide a certificate is to use a certificate object via the following cmdlet:
Connect-ExchangeOnline -Certificate <%X509Certificate2 Object%> -AppID "36ee4c6c-0812-40a2-b820-b22ebd02bce3" -Organization "contoso.onmicrosoft.com"
When the Certificate parameter is used, the certificate does not need to be installed on the computer where the command is executed. This parameter is applicable for scenarios where the certificate object is stored remotely and fetched at runtime during script execution. An example of this is when the certificate is stored in the Azure Key Vault.