Private/Auth/Invoke-MgcDeviceCodeAuth.ps1

function Invoke-MgcDeviceCodeAuth {
    <#
    .SYNOPSIS
        OAuth 2.0 Device Authorization Grant (RFC 8628).

    .DESCRIPTION
        - Requests a device + user code from /devicecode.
        - Displays the verification URL + user code.
        - Polls /token at the device-code's interval until success, expiration, or denial.

        NOTE: Device code is increasingly blocked by Conditional Access policies. Prefer
        Interactive flow whenever a browser on the same machine is available.
    #>

    [CmdletBinding()]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingWriteHost','',
        Justification = 'Device code flow requires displaying user_code to the operator.')]
    param(
        [Parameter(Mandatory)][string]$LoginEndpoint,
        [Parameter(Mandatory)][string]$TenantSegment,
        [Parameter(Mandatory)][string]$ClientId,
        [Parameter(Mandatory)][string[]]$Scopes
    )

    $deviceUrl = "$LoginEndpoint/$TenantSegment/oauth2/v2.0/devicecode"
    $tokenUrl  = "$LoginEndpoint/$TenantSegment/oauth2/v2.0/token"

    $deviceBody = @{
        client_id = $ClientId
        scope     = ($Scopes -join ' ')
    }

    try {
        $device = Invoke-RestMethod -Uri $deviceUrl -Method POST -Body $deviceBody `
            -ContentType 'application/x-www-form-urlencoded' -ErrorAction Stop
    } catch {
        throw "Device code request failed: $($_.Exception.Message)"
    }

    Write-Host ''
    Write-Host $device.message -ForegroundColor Yellow
    Write-Host ''

    $interval = if ($device.interval) { [int]$device.interval } else { 5 }
    $deadline = (Get-Date).AddSeconds([int]$device.expires_in)

    while ((Get-Date) -lt $deadline) {
        Start-Sleep -Seconds $interval
        try {
            $body = @{
                client_id   = $ClientId
                grant_type  = 'urn:ietf:params:oauth:grant-type:device_code'
                device_code = $device.device_code
            }
            return Invoke-MgcTokenEndpoint -Url $tokenUrl -Body $body
        } catch {
            $msg = $_.Exception.Message
            if ($msg -match 'authorization_pending') { continue }
            if ($msg -match 'slow_down') { $interval += 5; continue }
            if ($msg -match 'expired_token|access_denied') { throw $msg }
            throw $msg
        }
    }
    throw "Device code expired before the user completed sign-in."
}