Public/Invoke-DeviceAuthorizationFlow.ps1
<# .SYNOPSIS Invokes the OAuth 2.0 Device Authorization Flow. .DESCRIPTION This function initiates the OAuth 2.0 Device Authorization Flow, which is typically used for devices with limited input capabilities. It interacts with an authorization server to obtain access tokens. .PARAMETER ClientId Specifies the client ID of the application. .PARAMETER Tenant Specifies the tenant ID or name. If the name is provided, it will be resolved to the corresponding tenant ID (Entra ID tenants only). .PARAMETER Scope A space-separated list of scopes that you want the user to consent to. .PARAMETER DeviceCodeEndpoint (Optional) Specifies the endpoint to request a device code. If not specified, the default value is "https://login.microsoftonline.com/{tenant}/oauth2/v2.0/devicecode". .PARAMETER TokenEndpoint (Optional) Specifies the endpoint to request an access token. If not specified, the default value is "https://login.microsoftonline.com/{tenant}/oauth2/v2.0/token". .NOTES https://learn.microsoft.com/en-us/entra/identity-platform/v2-oauth2-device-code .EXAMPLE Invoke-DeviceAuthorizationFlow -ClientId "your-client-id" -Tenant "your-tenant-id" -Scope "openid profile email" This example demonstrates how to initiate the device authorization flow using the specified client ID, tenant ID, and scopes. .EXAMPLE Invoke-DeviceAuthorizationFlow -ClientId "your-client-id" -Tenant "your-tenant-name" -Scope "openid profile email" -DeviceCodeEndpoint "https://custom-endpoint/devicecode" -TokenEndpoint "https://custom-endpoint/token" This example demonstrates how to use custom endpoints for obtaining the device code and access token. #> function Invoke-DeviceAuthorizationFlow { param ( [Parameter(Mandatory)] [string]$ClientId, [Parameter(Mandatory)] [string]$Tenant, [Parameter(Mandatory)] [string]$Scope, [string]$DeviceCodeEndpoint, [string]$TokenEndpoint ) $GrantType = "urn:ietf:params:oauth:grant-type:device_code" # Resolve tenant ID if a tenant name is provided if ($Tenant -notmatch "^[0-9a-fA-F-]{36}$") { $Tenant = Get-MsftTenantId -TenantName $Tenant } # Entra ID default, if no device code endpoint has been provided if ([string]::IsNullOrEmpty($DeviceCodeEndpoint)) { $DeviceCodeEndpoint = "https://login.microsoftonline.com/$Tenant/oauth2/v2.0/devicecode" } # Entra ID default, if no token endpoint has been provided if ([string]::IsNullOrEmpty($TokenEndpoint)) { $TokenEndpoint = "https://login.microsoftonline.com/$Tenant/oauth2/v2.0/token" } $ContentType = "application/x-www-form-urlencoded" $authRequestBody = @{ client_id = $ClientId scope = $Scope } try { $authResponse = Invoke-RestMethod -Method Post -Uri $DeviceCodeEndpoint -ContentType $ContentType -Body $authRequestBody } catch { <#Do this if a terminating exception happens#> } if ($null -ne $authResponse) { $verificationUri = $authResponse.verification_uri $deviceCode = $authResponse.device_code $expiresIn = $authResponse.expires_in $pollingInterval = $authResponse.interval $ExpirationTime = (Get-Date).AddSeconds($expiresIn) Write-Host $authResponse.message -ForegroundColor Green Start-Process $verificationUri $accTokenParam = @{ TokenUrl = $TokenEndpoint ClientId = $ClientId GrantType = $GrantType DeviceCode = $deviceCode } while ($null -eq $tokenResponse -and (Get-Date) -lt $ExpirationTime) { Start-Sleep -Seconds $pollingInterval try { $tokenResponse = Get-AccessToken @accTokenParam -ErrorAction Stop } catch { $errorCategory = ($_.errordetails.message | convertfrom-json).error switch ($errorCategory) { "authorization_pending" { Write-Host -ForegroundColor Green "Waiting for authorization..." } "invalid_client" { throw "Invalid client: Most likely your application is configured as confidential client." } Default {} } } } return $tokenResponse } } |