src/_Get-CciEntraToken.ps1
|
function _Get-CciEntraToken { <# .SYNOPSIS Acquires an Entra access token via the OAuth 2.0 device authorization grant, using plain REST. .DESCRIPTION Deliberately does NOT use Az.Accounts. On a freshly built Windows machine (Windows PowerShell 5.1) Connect-AzAccount -UseDeviceAuthentication fails with "The type initializer for 'Microsoft.Identity.Client.Platforms.net.MsalJsonSerializerContext' threw an exception", and installing Az.Accounts + Az.Storage costs hundreds of MB during OOBE for two REST calls. The device-code grant is a handful of HTTP requests, so cciget speaks it directly. .PARAMETER TenantId Entra tenant to authenticate against. .PARAMETER Scope Resource scope, e.g. https://storage.azure.com/.default .PARAMETER ClientId Public client application id used for the device-code flow. .OUTPUTS The access token string, or $null on failure. #> [CmdletBinding()] param( [Parameter(Mandatory)][string]$TenantId, [Parameter(Mandatory)][string]$Scope, [Parameter(Mandatory)][string]$ClientId ) $authority = "https://login.microsoftonline.com/$TenantId/oauth2/v2.0" try { $dc = Invoke-RestMethod -Method Post -Uri "$authority/devicecode" ` -ContentType 'application/x-www-form-urlencoded' ` -Body @{ client_id = $ClientId; scope = "$Scope offline_access" } -ErrorAction Stop } catch { Write-Warning "cciget: could not start device-code sign-in: $_" return $null } Write-Host '' Write-Host ' ------------------------------------------------------------' Write-Host " To sign in, open $($dc.verification_uri)" Write-Host " and enter the code: $($dc.user_code)" Write-Host ' ------------------------------------------------------------' Write-Host '' $interval = [int]$dc.interval if ($interval -lt 1) { $interval = 5 } $deadline = (Get-Date).AddSeconds([int]$dc.expires_in) while ((Get-Date) -lt $deadline) { Start-Sleep -Seconds $interval try { $tok = Invoke-RestMethod -Method Post -Uri "$authority/token" ` -ContentType 'application/x-www-form-urlencoded' ` -Body @{ grant_type = 'urn:ietf:params:oauth:grant-type:device_code' client_id = $ClientId device_code = $dc.device_code } -ErrorAction Stop if ($tok.access_token) { Write-Host 'cciget: sign-in complete.' return $tok.access_token } } catch { # The token endpoint answers 400 with an error code until the user # finishes; only authorization_pending/slow_down are non-fatal. $body = $null try { $stream = $_.Exception.Response.GetResponseStream() $reader = New-Object System.IO.StreamReader($stream) $body = $reader.ReadToEnd() | ConvertFrom-Json } catch { } $code = if ($body) { $body.error } else { $null } switch ($code) { 'authorization_pending' { continue } 'slow_down' { $interval += 5; continue } default { $desc = if ($body) { $body.error_description } else { $_.Exception.Message } Write-Warning "cciget: device-code sign-in failed ($code): $desc" return $null } } } } Write-Warning 'cciget: device-code sign-in timed out.' return $null } |