Connecting to Exchange Online PowerShell via client secret

Microsoft just released a new version of the Exchange Online (V2) PowerShell module, which brings support for much awaited feature – seamless connectivity that satisfies MFA requirements thanks to using the certificate-based authentication flow. Now, one can argue that this isn’t “true” MFA and point to the inherit auditing issues when using this flow, but that’s true for all other apps leveraging it, and not something the Exchange team can be blamed for.

We did a thorough review/guide of the new method in our recent post over at the Quadrotech blog. Here I’ll add few more details that were left out for brevity or simply border the “unsupported” frontier and should not be used unless you really want to get your hands dirty. We start by getting the obvious thing out of the way – yes, you can actually use a client secret as well, although it is considered less secure and Microsoft chose not to support it via the module. But nothing is stopping you from generating a client secret for your app, then using it to connect via the “manual” method outlined below. But more on that later.

First, what is this “manual” method I talk about? Well, if you are familiar with the way Exchange Online Remote PowerShell implements support for modern authentication (or have seen my previous articles on the subject), you are aware that the bulk of the client-side changes are introduced to handle the token request/renewal process. Once the token is obtained, it is “proxied” via the good old basic authentication WinRM endpoint, to a slightly different URI – one that has the “BasicAuthToOAuthConversion=true” value added. The backend of course has also received some updates in order to handle this, but those are out of our hands, and thus not as interesting.

The important thing is that once you understand the process, you can bypass the built-in cmdlets and create your own “connect to Exchange Online via modern authentication” function, which might suit your needs better. Same applies to the features released in the new version of the module. Once you have configured the Azure AD application, it’s permissions and the certificate to be used for authentication, you can use any number of methods to obtain a token, for example:

Add-Type -Path 'C:\Program Files\WindowsPowerShell\Modules\AzureAD\2.0.1.10\Microsoft.IdentityModel.Clients.ActiveDirectory.dll'

$authContext45 = New-Object "Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext" -ArgumentList "https://login.windows.net/tenant.onmicrosoft.com"
$secret = Get-ChildItem cert://currentuser/my/"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
$CAC = [Microsoft.IdentityModel.Clients.ActiveDirectory.ClientAssertionCertificate]::new("xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",$secret)
$authenticationResult = $authContext45.AcquireTokenAsync("https://outlook.office365.com",$CAC)

where I have pointed to a certificate stored in the Personal store for the current user and also provided the client_id of the application registered in Azure AD. If everything checks out and a valid token is obtained, you are ready to establish the remote PowerShell session. One small detail you need to know first – when passing the token, you need to specify the username in the following format: “OAuthUser@tenantGUID”. Here’s how it works:

$token = $authenticationResult.Result.AccessToken
$Authorization = "Bearer {0}" -f $Token
$Password = ConvertTo-SecureString -AsPlainText $Authorization -Force
$Ctoken = New-Object System.Management.Automation.PSCredential -ArgumentList "OAuthUser@923712ba-352a-4eda-bece-09d0684d0cfb",$Password

$Session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri https://outlook.office365.com/PowerShell-LiveId?BasicAuthToOAuthConversion=true -Credential $Ctoken -Authentication Basic -AllowRedirection -Verbose
Import-PSSession $Session

And with that, you should have an Exchange Online Remote PowerShell session established and all the cmdlets corresponding to the admin role granted to the application’s service principal available.

A variation of the above can be used to point to a certificate file, instead of using the certificate store provider. Assuming we have a cert.pfx file, protected with a password, the following example can be used:

$authContext45 = New-Object "Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext" -ArgumentList "https://login.windows.net/tenant.onmicrosoft.com"

$certFile = "C:\cert.pfx"
$certFilePassword = "P4$$w0rd"
$secret = New-Object -TypeName System.Security.Cryptography.X509Certificates.X509Certificate -ArgumentList $certFile,$certFilePassword
$CAC = [Microsoft.IdentityModel.Clients.ActiveDirectory.ClientAssertionCertificate]::new("xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxx",$secret)

$authenticationResult = $authContext45.AcquireTokenAsync("https://outlook.office365.com", $CAC)

Or if you don’t want to get bothered by certificates and don’t care about the increased security they provide, you can simply add a client secret to the application you registered in Azure AD and use the client credentials flow instead:

$authContext4 = New-Object "Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext" -ArgumentList "https://login.windows.net/tenant.onmicrosoft.com"
$ccred4 = New-Object Microsoft.IdentityModel.Clients.ActiveDirectory.ClientCredential -ArgumentList "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", "very_long_string"
$authenticationResult4 = $authContext4.AcquireTokenAsync("https://outlook.office365.com", $ccred4)

And that’s pretty much it. Once you have a valid token, use it to establish the remote PowerShell session via the sample above. Don’t forget to add some proper error handling and most importantly, logic to detect token expiry and renew/obtain new tokens as needed!

One additional detail you might need when troubleshooting connectivity issues – the token must contain the wid claim and within it, the admin role(s) to which the service principal object corresponding to your Azure AD application has been assigned. You already know how to get the token, as for decoding it use a site such as jwt.ms or just do it directly via PowerShell as detailed in this article. Here’s how a sample valid token would look like:

This entry was posted in Exchange Online, Office 365, PowerShell. Bookmark the permalink.

9 Responses to Connecting to Exchange Online PowerShell via client secret

  1. Brad says:

    Vasil, can you provide (from start to finish) what’s necessary to connect to Exchange online via a app id and secret? I’m having trouble understanding which of each block of code needs to be used to get this working, as I can’t get it to work. Thank you

    • Brad says:

      I’m getting a 403, access denied with this. Where I have [xxx] I’ve replaced specific user values

      Add-Type -Path ‘C:\Users\[xxx]\Documents\Powershell\ExOnline\Microsoft.IdentityModel.Clients.ActiveDirectory.dll’
      $authContext4 = New-Object “Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext” -ArgumentList “https://login.windows.net/[xxx].onmicrosoft.com”
      $ccred4 = New-Object Microsoft.IdentityModel.Clients.ActiveDirectory.ClientCredential -ArgumentList “[xxx]”, “[xxx]”
      $authenticationResult4 = $authContext4.AcquireTokenAsync(“https://outlook.office365.com”, $ccred4)
      $token = $authenticationResult4.Result.AccessToken
      $Authorization = “Bearer {0}” -f $Token
      $Password = ConvertTo-SecureString -AsPlainText $Authorization -Force
      $Ctoken = New-Object System.Management.Automation.PSCredential -ArgumentList “OAuthUser@[xxx]”,$Password

      $sessionCloud = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri https://outlook.office365.com/PowerShell-LiveId?BasicAuthToOAuthConversion=true -Credential $Ctoken -Authentication Basic -AllowRedirection -Verbose
      Import-PSSession $sessionCloud

      • Vasil Michev says:

        Check the token, most likely you don’t have a proper Azure AD role assigned to the service principal object.

  2. Mauro says:

    Hi Vasil,

    I’ve had issues with certain cmdlets. Specifically when running Add-RecipientPermission I get an error that says:

    ‘Active Directory operation failed on xxxxx.prod.outlook.com. This error is not retriable. Additional information: Access is denied.
    Active directory response: 00000005: SecErr: DSID-03152612, problem 4003 (INSUFF_ACCESS_RIGHTS), data 0

    However, if I run the cmdlet Add-MailboxPermission that one goes through fine without permissions issues. what’s going on here? I’m authing with a cert and granted global administrator and Exchange Admin roles to the application’s service principal.

    Thank you for any help that you can provide.

    • Vasil Michev says:

      Seems to run fine here. Perhaps you have some scopes restricting the use of Add-RecipientPermission? Can you run it fine when connecting via “regular” PS? In any case, you can report it to MS.

  3. Anishka says:

    Hi Michev,

    I am able to using the client credentials and client id for MFA enabled account and connect to exchange powershell using below command:-
    $Session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri https://outlook.office365.com/PowerShell-LiveId?BasicAuthToOAuthConversion=true -Credential $Ctoken -Authentication Basic -AllowRedirection -Verbose
    Im

    Can you please tell me that this approach is recommended by Microsoft for connection or they are going to disable this approach in future?

    • Vasil Michev says:

      It’s neither recommended not supported, use at your own risk. What’s supported is to use the module.

Leave a Reply to Mauro Cancel reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.