Authentication Flows: Device Code

Obtaining an Access Token for Microsoft Graph using the Device Code grant and PowerShell with the Invoke-RestMethod cmdlet.

PowerShell

#region Supporting Functions
function Get-TokenUsingDeviceFlow {
    [CmdletBinding()]
    param
    (
        [Parameter(
            Mandatory = $true,
            ValueFromPipelineByPropertyName = $true,
            Position = 1)]
        [string] $TenantId,
        [Parameter(
            Mandatory = $true,
            ValueFromPipelineByPropertyName = $true,
            Position = 0)]
        [string] $ClientId,
        [Parameter(
            Mandatory = $true,
            ValueFromPipelineByPropertyName = $true,
            Position = 1)]
        [string] $Scope
    )
    process {
        $scope = $Scope
        $uri = "https://login.microsoftonline.com/$TenantId/oauth2/v2.0/devicecode"
        $headers = @{}
        $headers.Add("Content-Type", "application/x-www-form-urlencoded")
        $body = @{
            scope     = $Scope
            client_id = $ClientId
        }
        $deviceCodeRequest = Invoke-RestMethod -Uri $uri -Method POST -Headers $headers -Body $body
        $expires = (Get-Date).AddSeconds($deviceCodeRequest.expires_in)
        $deviceCodeRequest | Add-Member -NotePropertyName expires -NotePropertyValue $expires
        Write-Host $deviceCodeRequest.message
        # Request Token
        $uri = " https://login.microsoftonline.com/$TenantId/oauth2/v2.0/token"
        $body = @{
            tenant      = $tenantId
            grant_type  = 'urn:ietf:params:oauth:grant-type:device_code'
            client_id   = $clientId
            device_code = $deviceCodeRequest.device_code
        }
        $bearerAccessToken = $null
        if ($deviceCodeRequest.interval) {
            do {
                Start-Sleep -Seconds $deviceCodeRequest.interval
                $errMsg = $null
                try {
                    $bearerAccessToken = Invoke-RestMethod -Uri $uri -Method POST 
                    -Headers $headers -Body $body
                    $expires = (Get-Date).AddSeconds($bearerAccessToken.expires_in)
                    $bearerAccessToken | Add-Member 'Expires' $expires
                }
                catch {
                    if ($_.Exception -is [System.Net.Http.HttpRequestException]) {
                        $errMsg = $_.ErrorDetails
                        $errMsg = (ConvertFrom-Json $errMsg).error
                        Write-Host $errMsg
                        if ($errMsg -eq 'invalid_client') {
                            $msg = "Does $clientId allow public client flows?"
                            Write-Host -ForegroundColor Yellow $msg
                            throw $_
                        }
                    }
                    else {
                        throw $_
                    }
                }
            } while ($errMsg -eq 'authorization_pending')
        }
        $bearerAccessToken
    }
}
#endRegion
$tenantId = 'common'
# Client that allows public flows i.e. Azure AD PowerShell
$clientId = '1b730954-1685-4b74-9bfd-dac224a7b894'
$msGraphScope = 'https://graph.microsoft.com/.default'
$aadGraphScope = 'https://graph.windows.net/.default'
$msGraphToken = Get-TokenUsingDeviceFlow -TenantId $tenantId `
    -ClientId $clientId -Scope $msGraphScope
$aadToken = Get-TokenUsingDeviceFlow -TenantId $tenantId `
    -ClientId $clientId -Scope $aadGraphScope