Version 3.1.0 of the ExchangeOnlineManagement
modules introduced a new option in the Connect-ExchangeOnline
cmdlet. You can now pass it an access token.
Here’s how I went about using it.
First, I created an App Registration with this delegated permission:
Did the admin consent. And under “Authentication” I enabled public client flows. (This is not strictly needed, but I use device code flow to authenticate later so it’s needed).
I also went to the corresponding Enterprise Application and limited it to my admin account only.
Next, I used the function I created a while ago for authenticating using the device code flow.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 |
function Get-MSDeviceToken { <# .SYNOPSIS Retrieve an OAuth2 token for the Microsoft Identity Platform using device authorization grant flow. .DESCRIPTION Retrieve an OAuth2 token for the Microsoft Identity Platform using device authorization grant flow. The token is for the specified Tenant, AppId, and Scopes. As part of the flow you will have to visit a Url in a browser (which can be on a different machine) and enter a code as well as sign in with the appropriate credentials of that Tenant. .PARAMETER TenantId The Id of the Azure AD Tenant. This parameter is mandatory. .PARAMETER AppId The Application Id of the App Registration for which the Token is meant. This Application Id will be present as an "audience" claim in the Token and it can only be used against that Application. This parameter is mandatory. .PARAMETER Scope A space separated list of Scopes for the Token. This parameter is mandatory. .EXAMPLE Get-MSDeviceToken -TenantId 2d29b111-19db-458d-83ff-1af0ac9ae35c -AppId 51869876-308a-4007-9f48-7c21165a5605 -Scope "User.Read openid" .LINK https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-device-code #> [CmdletBinding()] param( [Parameter(Mandatory=$true)] [string]$TenantId, [Parameter(Mandatory=$true)] [Alias("ClientId")] [string]$AppId, [Parameter(Mandatory=$true)] [string]$Scope ) $authUrl = "https://login.microsoftonline.com/$tenantId" # From https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-device-code # Device authorization request $authBody = @{ "tenant" = "$TenantId"; "client_id" = "$AppId"; "scope" = $Scope } $response = Invoke-RestMethod -Method POST -Uri "$authUrl/oauth2/v2.0/devicecode" -Body $authBody Write-Host $response.Message Set-Clipboard -Value $response.user_code $expiresMins = $response.expires_in / 60 Write-Host "The code is copied to your clipboard for ease of use. It expires in $expiresMins minutes." $waitInterval = $response.interval # Authenticating the user $authBody2 = @{ "tenant" = "$tenantId"; "grant_type" = "urn:ietf:params:oauth:grant-type:device_code"; "client_id" = "$appId"; "device_code" = $response.device_code } $authStatus = "waiting" $response2 = $null while ($authStatus -eq "waiting") { # Try to get the access token. if we encounter an error check the reason. # If the reason is we are waiting then sleep for some time. # If the reason is the user has declined or we timed out then quit. try { $response2 = Invoke-RestMethod -Method POST -Uri "$authUrl/oauth2/v2.0/token" -Body $authBody2 -ErrorAction Stop } catch { switch (($Global:Error[0].ErrorDetails.Message | ConvertFrom-Json).error) { "authorization_pending" { Write-Host "Waiting $waitInterval seconds for browser authentication to complete." Start-Sleep -Seconds $waitInterval } "authorization_declined" { # Note to self: this does not work if I decline in the browser. Not sure what use-case this is supposed to capture. Write-Host "Quitting as authorization was declined in browser." $authStatus = "failed" } "expired_token" { Write-Host "Authentication timed out." $authStatus = "failed" } "invalid_client" { Write-Host "Something wen't wrong. Have you enabled public client flows?" $authStatus = "failed" } } } if ($null -ne $response2) { $authStatus = "success" } } if ($authStatus -eq "failed") { $null } else { $response2 } } |
I ran it thus:
1 |
$result = Get-MSDeviceToken -TenantId "<tenant Id>" -AppId "<client Id of app registration>" -Scope "https://outlook.office.com/.default" |
I got the scope from this link.
This should do the following:
And once I authenticate by visiting the URL and entering the device code, the $result
object should have the access token.
I can now connect thus:
1 |
Connect-ExchangeOnline -AccessToken $result.access_token -Organization "<tenant Id>" |
And that works!