Hacking your way around Modern authentication and the PowerShell modules for Office 365

Most of the Office 365 PowerShell modules now support Modern authentication and that’s a very good thing. However, the implementation across the different modules leaves a lot to be desired because of the different approach taken by each team. For example, some of the modules do not support the Credentials parameter, other support it but automatically fallback to using legacy authentication when this parameter is invoked and so on. The table below summarizes the current landscape:

MFA status Pass credentials Pass token Bypass MFA on trusted location
Azure AD Supported Supported Supported Supported
Exchange Online (legacy) Not supported N/A N/A Not supported
Exchange Online (MFA module) Supported Not supported Not supported* Supported
Security and Compliance Center Not supported N/A N/A Not supported
SharePoint Online Supported Supported*** Not supported Not supported
SharePoint Online PnP Supported Supported*** Not supported Supported
Skype for Business Online Supported Supported*** Not supported* Supported
AIP/AADRM Supported Supported Supported Supported
Azure Supported Supported Supported Supported

The option to pass credentials is an important one, as you can combine it with different methods to bypass the need to perform second-factor authentication. For example, you can modify your claims rules to ensure that when the request is coming from a particular user, IP or application, no additional authentication will be required. Similarly, if you have configured second-factor authentication via Azure MFA, you can use the trusted locations functionality. This enables you to use the account to automate scripts or execute scheduled tasks, while at the same time still making sure it’s protected by additional authentication factors for generic requests.

(The asterisks in the Pass credentials column designate the modules which use legacy authentication when the Credentials parameter is invoked)

An alternative to this approach is to completely bypass the PowerShell modules and get an access token programmatically, then pass it directly, which is what we will discuss in the current article. Some of the modules do have support for passing a token as noted in the above table. The asterisks in the Pass token column indicate modules that do not have a parameter to pass the access token to, but for which certain workarounds exist.

Anyway, in order to get the token programmatically, one can use the ADAL binaries that come with the install of any of the above modules. For example, if you have the 2.0.0.98 version of the Azure AD PowerShell module installed, you can load the necessary DLL via:

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

The same DLL is shipped with each of the ADAL-enabled modules, however the version of the DLL might be different! In turn, this results in different methods exposed and even different authentication flows! This is one of the examples of the different ways modern authentication support has been implemented by different teams at Microsoft and one can only hope in the future things will change for the better.

Once the DLL is loaded, one can use the AcquireTokenAsync method to acquire a new access token. Some information is required beforehand though, such as the authority from which to request the token, the clientID of the application (Exchange Online for example), the resource you want access to. Unfortunately, there is no easy way to get the clientID, as Microsoft does not publish a list of the “server-side” applications. However, we can simply “hack” into what the ExO module does for example, and get all the needed information from a Fiddler trace. Or better yet, simply get it from the token cache:

$cache = [Microsoft.IdentityModel.Clients.ActiveDirectory.TokenCache]::DefaultShared
$cache.ReadItems() | select DisplayableId, Authority, ClientId, Resource

The screenshot above is taken after connecting to the Azure AD, ExO and SfBO PowerShell modules with Modern authentication enabled. For each of these, an access token was obtained and the token cache gives us information about the authority, clientID and Resource for which the token is valid. In effect, now we have all the needed information to get a token programmatically for each of these. Here’s how to get the token for ExO:

$authContext = New-Object "Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext" -ArgumentList https://login.microsoftonline.com/michev.onmicrosoft.com/
$client_id = "a0c73c16-a7e3-4564-9a95-2bdf47383716"
$Credential = Get-Credential huku@michev.onmicrosoft.com
$AADcredential = New-Object "Microsoft.IdentityModel.Clients.ActiveDirectory.UserPasswordCredential" -ArgumentList $Credential.UserName,$Credential.Password
$authResult = [Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContextIntegratedAuthExtensions]::AcquireTokenAsync($authContext,"https://outlook.office365.com",$client_Id,$AADcredential)

If everything went OK, the access token will be contained in the $authResult.Result.AccessToken output. Or, you can simply look at the token cache as we did above. And once you have the access token, you can then connect to ExO Remote PowerShell by using:

$Authorization = "Bearer {0}" -f $authResult.Result.AccessToken
$Password = ConvertTo-SecureString -AsPlainText $Authorization -Force
$Ctoken = New-Object System.Management.Automation.PSCredential -ArgumentList "huku@michev.onmicrosoft.com", $Password
$Session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri https://outlook.office365.com/PowerShell-LiveId?BasicAuthToOAuthConversion=true -Credential $Ctoken -Authentication Basic -AllowRedirection
Import-PSSession $Session

There’s one last piece of information that we needed in order to perform the above action, namely the ConnectionURI. You can see that it’s very similar to the “regular” one we’ve been using for years, however there is one additional parameter added at the end. Where do we get this information you ask? Simply look at the parameters of any ExO Remote PowerShell session you have created via the new, MFA-enabled module:

The procedure outlined above can be used to “reverse engineer” connecting to other Office 365 services via access token. Getting the access token itself can be performed by various means, using the different ADAL methods. For the purposes of automation, a solution that does not require any user interaction is preferred, so my recommended method is to configure the bypass, however you can also invoke the generic ADAL prompt and perform the login process interactively if needed.

Now, one important thing that needs to be made clear – this only covers the access token! Depending on the ADAL binaries used, a refresh token might not even be returned, so the session you establish using this method will be a short-lived one 🙂

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

6 Responses to Hacking your way around Modern authentication and the PowerShell modules for Office 365

  1. Stuart says:

    Hi Vasil.

    This blog post is really helpful and useful. Thanks so much for posting – Microsoft should really document this stuff. Any ideas how to address that last issue that you pointed out… We’re only getting an access token, not a refresh token. And so when the session times-out it prompts for a password and does not reconnect. Is there anyway to overcome this? I am using the ADAL binaries from the Azure AD PowerShell module (2.0.0.131). Is there a way to check if the token has expired and refresh it?

    • Vasil Michev says:

      Afaik no. They simply removed the functionality to store/reuse refresh tokens when invoking the ADAL methods manually. Then again, I’m hardly an expert on ADAL, and I’m not even a programmer, so don’t take my word for it 🙂

      The token cache ($cache.ReadItems() in the examples above) will give you information about the currently stored tokens. If you do it manually, no refresh token is stored. If you use the built-in methods (for example if you use the ADAL-enabled ExO module), the token cache will store a refresh token as well.

  2. Stuart says:

    Thanks Vasil. We’ve submitted a design change request to Microsoft to add support for passing local credentials to the Exchange Online MFA module. Hopefully they will take it in to consideration – especially given the other O365 online modules already have such support. Meanwhile your approach will allow us to configure automation scripts via Scheduled Tasks and service accounts! Thanks again.

  3. Pingback: Security and Compliance Center PowerShell finally supports Modern authentication | Blog

  4. Stuart says:

    Hi Vasil.

    I wanted to let you know that Microsoft accepted our DCR and have just released an update to the “Exchange Online (MFA module)” which now accepts passing credentials! I’ve tested and it works as expected. I was prompted to download the update when launching the “Microsoft Exchange Online Powershell Module” ClickOnce application. The Connect-EXOPSSession cmdlet now has a -Credential parameter.

    • Vasil Michev says:

      Thanks for sharing back Stuart, I do see the parameter now. And it seems to work just fine with bypassing MFA on trusted locations too!

      If you are forced to perform an MFA (or on untrusted location), you should not use it though:

      PS C:\Users\vasil> Connect-EXOPSSession -Credential $cred
      New-ExoPSSession : AADSTS50076: Due to a configuration change made by your administrator, or because you moved to a
      new location, you must use multi-factor authentication to access ‘00000002-0000-0ff1-ce00-000000000000’.

      Anyway, good addition, thank you guys for the DCR! 🙂

Leave a Reply

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