EWS and OAuth

Since world is moving towards Cloud and away from Basic authentication, I also have to address this in my scripts. With the latest announcement on The Microsoft Exchange Team Blog about the Upcoming changes to Exchange Web Services (EWS) API for Office 365, I get a lot of questions from people about this.

First of all: This change is ONLY for Office 365!

Besides this I appreciate this change and believe it or not with the latest Exchange versions you can use OAuth already on your on-premises environment.

In this post I describe how get your tokens using ADAL, which can be used for accessing a mailbox via EWS. Most of you might already used a tool, which supports OAuth, but weren’t aware of: EWS Editor

Prerequisites

Did you know that EWS Editor has already this functionality and that its AppID is available in O365? You just need to give consent, which looks like this

EWSOAuth_01

Option for using OAuth

EWSOAuth_02

User needs to give consent

Once you consent, you can retrieve a token and decode the AccessToken with your preferred tool. I’m using one of the following:

Note: Just copy the value of AccessToken from the result and paste it into the tools.

The decoded token looks like this:

EWSOAuth_03

As you can see, I got a scope with “Full_Access_As_User” for https://outlook.office365.com.

There are many ways how to retrieve a token. For me the simpliest way is using ADAL as it takes care about the handling, especially when the AccessToken expired and you need to retrive a new token using the RefreshToken.

Thus means you need to have ADAL somewhere installed on your computer. In my example I’m assuming you have the Exchange Online PowerShell Module installed.

Simple function

Update 06.08.2018:

I assumed that you would use the EXO MFA module. As this module uses ADAL v2, but the PowerShell Module AzureAD or AzureADPreview uses ADAL v3, you might get an error as some methods changed. I updated the function to use the proper methods, depending on the version.

Note: Once you’ve loaded ADAL you cannot use a different version of ADAL in the same PowerShell session. If you want to use another version, you would need to open a new PowerShell session. This is a limitation of PowerShell with assemblies, which is written here:

If the module includes an assembly (.dll), all members that are implemented by the assembly are removed, but the assembly is not unloaded.

I called my function Get-EWSOAuthtoken:

function Get-EWSOAuthtoken
{
    [CmdletBinding()]
    Param
    (
        [System.String]
        $UserPrincipalName,
 
        [System.String]
        $ADALPath,
 
        [System.String]
        $ClientId = '0e4bf2e2-aa7d-46e8-aa12-263adeb3a62b',
 
        [System.Uri]
        $ConnectionUri = 'https://outlook.office365.com/EWS/Exchange.asmx',
 
        [System.Uri]
        $RedirectUri = 'https://microsoft.com/EwsEditor',
 
        [ValidateSet('Always','Auto','Never','RefreshSession')]
        [System.String]
        $PromptBehavior = 'Auto',
 
        [System.Management.Automation.SwitchParameter]
        $TokenForResourceExists
    )
 
Begin
{
    try
    {
        If([System.String]::IsNullOrEmpty($ADALPath))
        {
            $ADALPath = (Get-ChildItem -Path ($env:LOCALAPPDATA +'\Apps\2.0') -Recurse -Include Microsoft.IdentityModel.Clients.ActiveDirectory.dll | Select-Object -First 1)
        }
        Import-Module $ADALPath -Force 
    }
    catch
    {
        #create object
        $returnValue = New-Object -TypeName PSObject
        #get all properties from last error
        $ErrorProperties = $Error[0] | Get-Member -MemberType Property
        #add existing properties to object
        foreach ($Property in $ErrorProperties)
        {
            if ($Property.Name -eq 'InvocationInfo')
            {
                $returnValue | Add-Member -Type NoteProperty -Name 'InvocationInfo' -Value $($Error[0].InvocationInfo.PositionMessage)
            }
            else
            {
                $returnValue | Add-Member -Type NoteProperty -Name $($Property.Name) -Value $($Error[0].$($Property.Name))
            }
        }
        #return object
        $returnValue
        break
    }
}
Process
{
    try
    {
        $resource = $connectionUri.Scheme + [System.Uri]::SchemeDelimiter + $connectionUri.Host
        If ($TokenForResourceExists)
        {
            [System.Boolean]$result = $false
            #get existing tokens
            $TokenCache = ([Microsoft.IdentityModel.Clients.ActiveDirectory.TokenCache]::DefaultShared).ReadItems()
            If($TokenCache.Count -gt 0)
            {
                ForEach($Token in $TokenCache)
                {
                    If($Token.Resource -eq $resource)
                    {
                        $result = $true
                        break
                    }
                }
            }
        }
        Else
        {
            $azureADAuthorizationEndpointUri = 'https://login.windows.net/common'
            $AuthContext = New-Object Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext($azureADAuthorizationEndpointUri)
            If (-not [System.String]::IsNullOrEmpty($UserPrincipalName))
            {
                $UserID = [Microsoft.IdentityModel.Clients.ActiveDirectory.UserIdentifier]::new($UserPrincipalName,'RequiredDisplayableId')
            }
            Write-Verbose "FileVersion:$((Get-Item $ADALPath).VersionInfo.FileVersion)"
            If ((Get-Module -Name  Microsoft.IdentityModel.Clients.ActiveDirectory).Version.Major -lt 3)
            {
                Write-Verbose "Looks like ADALv2"
                $ADALv2PromptBehavior = [Microsoft.IdentityModel.Clients.ActiveDirectory.PromptBehavior]::Always
                If ($UserID)
                {
                    $token = $AuthContext.AcquireToken($resource,$clientId,$redirectUri,$ADALv2PromptBehavior,$UserID)
                }
                Else
                {
                    $token = $AuthContext.AcquireToken($resource,$clientId,$redirectUri,[Microsoft.IdentityModel.Clients.ActiveDirectory.PromptBehavior]::$PromptBehavior)
                }
            }
            Else
            {
                Write-Verbose "Looks like ADALv3"
                $ADALv3PromptBehavior = New-Object Microsoft.IdentityModel.Clients.ActiveDirectory.PlatformParameters -ArgumentList $PromptBehavior
                If ($UserID)
                {
                    $token = ($AuthContext.AcquireTokenAsync($resource,$clientId,$redirectUri,$ADALv3PromptBehavior,$UserID)).Result
                }
                Else
                {
                    $token = ($AuthContext.AcquireTokenAsync($resource,$clientId,$redirectUri,$ADALv3PromptBehavior)).Result
                }
            }
        }
    }
    catch
    {
        #create object
        $returnValue = New-Object -TypeName PSObject
        #get all properties from last error
        $ErrorProperties =$Error[0] | Get-Member -MemberType Property
        #add existing properties to object
        foreach ($Property in $ErrorProperties)
        {
            if ($Property.Name -eq 'InvocationInfo')
            {
                $returnValue | Add-Member -Type NoteProperty -Name 'InvocationInfo' -Value $($Error[0].InvocationInfo.PositionMessage)
            }
            else {
                $returnValue | Add-Member -Type NoteProperty -Name $($Property.Name) -Value $($Error[0].$($Property.Name))
            }
        }
        #return object
        $returnValue
        break
    }
}
End
{
    If($TokenForResourceExists)
    {
        $result
    }
    Else
    {
        $token
    }
}
}

 

Whith this you can easily get your token:

EWSOAuth_04

Assuming you store the token in a variable, e.g.: $token, you can leverage OAuthCredentials Class of Microsoft.Exchange.WebServices.dll.

Here’s a coe snippet from one of my latest scripts, where I use OAuthCredentials for my EWS service object:

ElseIf ($UseOAuth){
    try{
        $AutoDV2 = Get-AutoDV2 -EmailAddress $MailboxName -Protocol EWS
        $token = Get-EWSOAuthtoken -UserPrincipalName $UserPrincipalName -ADALPath $ADALPath -ClientId $ClientId -ConnectionUri $AutoDV2.Url -RedirectUri $RedirectUri -PromptBehavior $PromptBehavior
    }
    catch{
        #create object
        $returnValue = New-Object -TypeName PSObject
        #get all properties from last error
        $ErrorProperties =$Error[0] | Get-Member -MemberType Property
        #add existing properties to object
        foreach ($Property in $ErrorProperties){
            if ($Property.Name -eq 'InvocationInfo'){
                $returnValue | Add-Member -Type NoteProperty -Name 'InvocationInfo' -Value $($Error[0].InvocationInfo.PositionMessage)
            }
            else {
                $returnValue | Add-Member -Type NoteProperty -Name $($Property.Name) -Value $($Error[0].$($Property.Name))
            }
        }
        #return object
        $returnValue
    }
    $service.Credentials = [Microsoft.Exchange.WebServices.Data.OAuthCredentials]$token.AccessToken
    $service.Url = $AutoDV2.Url
}

 

What about on-premises?

I previously mentioned you can use OAuth also for on-premises. But for this some more steps needs to be done:

  • make sure you have a healthy Hybrid setup
  • HMA was announced by the Exchange Team here. You don’t have to enable HMA completely to make this happen. Don’t do it when you’re not ready! You only need to add the on-premises web service URLs as SPNs to Azure AD as described in the blog post or in the KB here
  • make sure your virtual directories have OAuthAuthentication set to True

With this you can retrieve now AccessToken for your on-premises URLs:

EWSOAuth_05

Conclusion

I hope this helps you and clarifies some questions. I can only encourage you start working with OAuth. How you use your own ClientID and not the one from EWS Editor will be covered in another post, where I describe how I granted a LOB application access across on-premises and EXO.

Advertisements

10 thoughts on “EWS and OAuth

  1. Pingback: EWS and OAuth | The clueless guy – JC's Blog-O-Gibberish

  2. Thanks for the really useful post. Unfortunately I am not able to get it working. I am using the ADAL library from the latest version of the Azure AD PowerShell module (2.0.1.6) and then I try to use the function to generate a token I get the following error:

    Exception: System.Management.Automation.RuntimeException: Method invocation failed because
    [Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext] does not contain a method named ‘AcquireToken’. at System.Management.Automation.ExceptionHandlingOps.CheckActionPreference(FunctionContext funcContext, Exceptionexception)
    at System.Management.Automation.Interpreter.ActionCallInstruction`2.Run(InterpretedFrame frame)
    at System.Management.Automation.Interpreter.EnterTryCatchFinallyInstruction.Run(InterpretedFrame frame)
    at System.Management.Automation.Interpreter.EnterTryCatchFinallyInstruction.Run(InterpretedFrame frame)
    FullyQualifiedErrorId : MethodNotFound

    It appears the ADAL library no longer supprots the ‘AcquireToken’ method. I’ve seen a few references to this on the web but I don’t really understand how to resolve. Any ideas or thoughts on how to resolve? I think we need to use a different method but I don’t really know which one or what arguments to use.

    Like

    • Hi Stuart,
      you’re right. I always used the ADAL from the EXO module As I assumed, when someone is doing something with EXO, this module will be als installed on the person’s machine. I just realized, that this is ADALv2, while the AzureAD module uses ADALv3 and here this method isn’t available.
      Thanks for this catch! I’ll look into this to make it working with both. Meanwhile just install the EXO MFA module and you should be good to go.
      Ciao,
      Ingo

      Like

      • Thanks Ingo. I actually have the EXO module installed but had changed the code to use the Azure AD module DLL. Strangely enough when I changed it back to use the EXO module it still fails with the same error. I’ll try again on Monday. Would be great to get it working with ADAL v3 too. I assumed I would need the AcquireTokenASync method but that seems to have different parameters and I couldn’t get it working.

        Like

      • Hi Stuart,
        that’s by design as I don’t use -Force switch for Import-Module. Thus when you had this module imported before it still reference to the other one. Also one additional thing. When you open a new PowerShell it should work.
        Update 06.08.2018:
        I’ve updated the function to distinct between ADAL v2 and ADAL v3. I also just learned that assemblies (.DLLs) cannot be unloaded from a session. Thus you would need to start a new PowerShell session in order to use a different version.
        Ciao,
        Ingo

        Like

  3. Hello Ingo, quite interesting and complex article! Can you share more detailed information how you can display and decode the token once you authenticated via OAuth to EXO? Actually the step between the second and third screenshot in your post …
    Is it part of EWS Editor?
    Thanks
    Robert

    Like

    • Hi Robert,
      I’ve updated the post. You just need to copy and paste the value of the property AccessToken from the result. Or when you use Fiddler, the value of the Bearer Authorization header. Use one of the sites mentioned above for decoding.
      Ciao,
      Ingo

      Like

  4. Thanks for making the updates to the example code Ingo. I didn’t realise it would be so simple. The other examples of ‘AquireTokenAsync’ method that I found online seemed to use a completely different syntax. I got it working with the v3 ADAL library from the Azure AD module. It even works with my MFA-enabled account.

    One last question… Can you explain the ‘-TokenForResourceExists’ switch parameter within your Get-EWSAuthToken function. When would you specify that parameter and what exactly does it do? I get that it is looks at the existing token cache but then it just returns as value of $true – what does that achieve? Thanks again.

    Like

    • Hi Stuart,
      I’m sure there are several ways how to retrieve an AccessToken. 😉
      The switch you mentioned was added for another script to check whether I already have a valid token for a specific resource and then take action on the result. Think about multiple resources you’re trying to access…I hope this is not too confusing.
      Ciao,
      Ingo

      Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s