Search code examples
azurepowershellpermissionsexchangewebservices

Powershell Exchange EWS script authentication using Oauth getting 401 error if application permission full_access_as_app is not granted


I'm trying to convert some EWS scripts to use oauth. I've found that it works when the Azure registered app has been given application permission full_access_as_app, but not when its only set to delegated permissions EWS.AccessAsUser.All . It seems that if full_access_as_app is given then anyone with the appID and secret can access any mailbox. When EWS.AccessAsUser.All is given no one can access any mailbox. What I was hoping for is that the appID and secret were essentially keys to the gate, but then access to a mailbox would be based on if the particular user running the script, or the credentials passed with it (which I don't know how to do unless it's the logged on user), has permissions to the mailbox. So, essentially, you get in the gate, but then you use impersonation. It also doesn't seem that the app secret can be held in a secure hash file like credentials can, so the secret is exposed. Thank you for your help.

## Request an access token

# Define AppId, secret and scope, your tenant name and endpoint URL
$AppId = 'AppId'
$AppSecret = 'AppSecret'
$Scope = "https://outlook.office365.com/.default"
$TenantName = "domain.onmicrosoft.com"
$Url = "https://login.microsoftonline.com/$TenantName/oauth2/v2.0/token"

# Add System.Web for urlencode
Add-Type -AssemblyName System.Web

# Create body
$Body = @{
    client_id = $AppId
    client_secret = $AppSecret
    scope = $Scope
    grant_type = 'client_credentials'
}

# Splat the parameters for Invoke-Restmethod for cleaner code
$PostSplat = @{
    ContentType = 'application/x-www-form-urlencoded'
    Method = 'POST'

    # Create string by joining bodylist with '&'
    Body = $Body
    Uri = $Url
}

# Request the token!
$Request = Invoke-RestMethod @PostSplat

#######################

$Email = "UserA@domain.com"

# Import "Microsoft Exchange Web Services Managed API 2.2"
Import-Module -Name "C:\Program Files\Microsoft\Exchange\Web Services\2.2\Microsoft.Exchange.WebServices.dll"

## Create the Exchange Service object with Oauth creds
$Service = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService -ArgumentList Exchange2013_SP1
$service.Url= new-object Uri("https://outlook.office365.com/EWS/Exchange.asmx")
$Service.TraceEnabled = $true
$service.ImpersonatedUserId = new-object Microsoft.Exchange.WebServices.Data.ImpersonatedUserId([Microsoft.Exchange.WebServices.Data.ConnectingIdType]::SmtpAddress,$Email)
$service.HttpHeaders.Add("X-AnchorMailbox", $Email)
$Service.Credentials = New-Object Microsoft.Exchange.WebServices.Data.OAuthCredentials($Request.access_token)

$OAuthCredentials = New-Object Microsoft.Exchange.WebServices.Data.OAuthCredentials($Request.access_token)
$service.Credentials = $OAuthCredentials

#####################

# WellKnown folders to adjust
$folderNames = "Inbox"

Foreach($folderName in $folderNames)
    {
        # Set the WellKnownFolder
        $FolderId = [Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::$folderName

        # Bind to WellKnownFolder Notes
        $folder = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($Service, $folderId)

        Write-Host "$($Email): $($folderName):  " -NoNewline
        $folder.archivetag.RetentionId.Guid     

    } # Foreach($folderName... END

######################

Solution

  • You should change to use delegated permission. With delegated permission, you will be able to access the API as a specific user.

    enter image description here

    So, you need to add EWS.AccessAsUser.All permission, and get a token as following:

    ## Request an access token
    
    # Define AppId, secret and scope, your tenant name and endpoint URL
    $AppId = 'your app id'
    $AppSecret = 'your app key'
    $Scope = "https://outlook.office365.com/.default"
    $TenantName = "{your_tenant}.onmicrosoft.com"
    $Url = "https://login.microsoftonline.com/$TenantName/oauth2/v2.0/token"
    
    # Add System.Web for urlencode
    Add-Type -AssemblyName System.Web
    
    # Create body
    $Body = @{
        client_id = $AppId
        client_secret = $AppSecret
        scope = $Scope
        grant_type = 'password'
        username = 'your user name here, e.g.,jack@hanxia.onmicrosoft.com'
        password = 'your password here ******************'
    }
    
    # Splat the parameters for Invoke-Restmethod for cleaner code
    $PostSplat = @{
        ContentType = 'application/x-www-form-urlencoded'
        Method = 'POST'
    
        # Create string by joining bodylist with '&'
        Body = $Body
        Uri = $Url
    }
    
    # Request the token for user!
    $Request = Invoke-RestMethod @PostSplat
    
    $Request.access_token