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/tenantname.onmicrosoft.com/
$client_id = "a0c73c16-a7e3-4564-9a95-2bdf47383716"
$Credential = Get-Credential user@tenantname.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 "user@tenantname.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.

22 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! 🙂

      • Nagamani says:

        Hi Vasil,

        i”m connecting O365 by passing Credentials —->
        Connect-EXOPSSession -Credential $Credential

        i’m getting below error , Please Help me…

        New-ExoPSSession : unknown_user_type: Unknown User Type nAt C:\Users\ilmhsqldbsvc\AppData\Local\Apps\2.0\D6ZQPBZQ.QYG\XNENGLCW.C6X\micr..tion_1975b8453054a2b5_0010.0000_10d85
        00
        8035862c6\CreateEXOPSSession.ps1:292 char:30
        + $PSSession = New-ExoPSSession -UserPrincipalName $UserPrincipalN …
        + ~~~~~~~~~
        + CategoryInfo : NotSpecified: (:) [New-ExoPSSession], AdalException
        + FullyQualifiedErrorId : Microsoft.IdentityModel.Clients.ActiveDirectory.AdalException,Microsoft.Exchange.Manag

  5. Mike says:

    This is working for me with ExO but I cannot figure out how to do it with a SharePoint Online instance where legacy auth is disabled. I’ve been tinkering with the connection string but now I am wondering: Is this even possible with this method?

  6. Pingback: Deep dive:Exchange Online PowerShell and MFA | The clueless guy

  7. Mark Wickens says:

    I would like to have a user log into my web application and generate a token for use with Exchange Online (MFA) in PowerShell. This post has gotten me almost there. But how do I get an OAuth access token without an application secret? I’m new to OAuth, so I’m probably missing something obvious.

    • Vasil Michev says:

      That depends on the type of application you have. For a “Web App/API” type of application (private client), you can only use the secret, as it uses the so-called app permissions. For a “Native” app (public client), which use the ‘delegated permissions’ model, you can log the users with their own credentials. I’m probably not explaining it correctly as I’m far from being a programmer, but you can refer to the documentation on this, for example: https://docs.microsoft.com/en-us/azure/active-directory/develop/active-directory-protocols-oauth-code

      • Mark Wickens says:

        Thanks. You’re right. For a native app, you don’t need the secret. For a web app (like mine), you do, unfortunately.

        I actually used Fiddler to see how Powershell was authenticating. Duplicating that method works in the Web app right up until it redirects to “urn:ietf:wg:oauth:2.0:oob” with the authorization code. Not helpful. :-/ And specifying a different redirect parameter doesn’t work, at least none that I’ve tried.

  8. Uma says:

    I am facing an error while executing lines to get access token.

    Cannot convert argument “ctx”, with value: “Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext”, for “AcquireTokenAsync” to type
    “Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext”: “Cannot convert the “Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext” value of type
    “Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext” to type “Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext”.”
    At line:5 char:1
    + $authResult = [Microsoft.IdentityModel.Clients.ActiveDirectory.Authen …
    + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo : NotSpecified: (:) [], MethodException
    + FullyQualifiedErrorId : MethodArgumentConversionInvalidCastArgument

    • Vasil Michev says:

      Keep in mind that this article is two years old by now and we have numerous updates to the ADAL binaries in the meantime. The example will work with the version found as part of the 2.0.0.98 Azure AD module, newer versions don’t even expose the UserPasswordCredential method anymore.

      • Uma says:

        Thank you for responding. I got this resolved. This error message was coming due to the clash between libraries from AzureAD and MSOnline. This particular DLL is found in both of these modules and I had loaded MSOnline one, which was causing the problem.
        But I have one more question related to client id. How can this id be same in other machines. . It also changed next day and the checked for the id in other machines for other user accounts. It is same for the day across machines. Could you please elaborate further on What is this client id exactly.

  9. Pingback: More on entries from other tenants visible in the Unified Audit log | Blog

  10. Samraj says:

    HI Vasil,
    I have an Microsoft O365 account configured with ADFS in local. All the users are remains same as like AD account. BASIC credentials are blocked for Powershell access. I have an user who can able to access the Security and compliance center only.

    Now I am trying to authenticate the Powershell commandlets in unattended manner.

    -Credentials parameter is not working with Connect-EXOPSSession command. Connect-IPPSSession command working fine. But this command doesn’t have option to do it unattended manner. I am trying to run the Compliance command with Oauth Token ( MSo token / AD token available with me).

    Can you help me to run the compliance command with Bearer token?

    I have tried all the scenario and this is the most commonly used.

    $Password = ConvertTo-SecureString -AsPlainText $Authorization -Force
    $token = New-Object System.Management.Automation.PSCredential -ArgumentList “”, $Password

    I tried the above scenrio and I am getting below error.

    $Session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri https://ps.compliance.protection.outlook.com -Credential $Ctoken -AllowRedirection
    WARNING: Your connection has been redirected to the following URI:
    “https://ps.compliance.protection.outlook.com/ucc/wsman ”
    New-PSSession : [ps.compliance.protection.outlook.com] Connecting to remote server
    ps.compliance.protection.outlook.com failed with the following error message : The WinRM client received an HTTP
    status code of 440 from the remote WS-Management service. For more information, see the about_Remote_Troubleshooting
    Help topic.
    At line:1 char:12
    + $Session = New-PSSession -ConfigurationName Microsoft.Exchange -Conne …
    + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo : OpenError: (System.Manageme….RemoteRunspace:RemoteRunspace) [New-PSSession], PSRemotin
    gTransportException
    + FullyQualifiedErrorId : -2144108273,PSSessionOpenFailed

    • Vasil Michev says:

      The method outlined above works just fine for the SCC PowerShell too. You are not using the correct URL though, should be https://ps.compliance.protection.outlook.com/PowerShell-LiveId?BasicAuthToOAuthConversion=true

      And you should have Basic auth enabled on the WinRM client, as evident from the URL, the way that PowerShell connects is by passing an OAuth token via the basic endpoint.

      • Samraj says:

        Hi Michev, Now I can successfully connected with server .Thanks for your help.

        Earlier i used the command (Connect-IPPSSession) to get the access token from cache. Now I am trying to have Azure AD application within the tenant. I can”t able to provide the necessary permission to run the Security and compliance command with the generated access token. Can I know how to setup the Azure AD application to support security and compliance command with access token. Access token generated in above method is expired in 1 hour. So i need to setup one more app to generate the access token with refresh token.

  11. Nikola Spasić says:

    Hi Vasil,

    I am trying to do something similar, but can’t figure it out. I have to get the html source of the portal.office.com web page, and was planning to use Invoke-WebRequest commandlet, but when I type:

    Invoke-WebRequest -Uri https://portal.office.com -UseBasicParsing

    I get the following response:

    StatusCode : 200
    StatusDescription : OK
    Content :

    Sign in to your account

    <meta http-eq…

    This is the Office365 login screen where I enter my credentials when I access from browser, but I can't figure out how to authenticate on that page from powershell. Basically, I don’t need access to Office365 apps, outlook, exchange or anything else, just to access portal like I’m accessing it from browser.

    • Vasil Michev says:

      The portal uses a different auth flow, I’m not aware of any way to automate getting a token for that.

Leave a Reply to Mike Cancel reply

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