Private/Get-FDAUserDelegatedToken.ps1

function Get-FDAUserDelegatedToken {
    <#
    .SYNOPSIS
        Acquire a user-delegated access token for a scope, reusing a cached
        refresh token so a single interactive sign-in covers every scope.
    .DESCRIPTION
        Strategy:
          1. If a refresh token from a prior sign-in is in module state, redeem
             it silently for the requested scope (AAD refresh tokens are not
             audience-bound, so one device-code sign-in for, say, Fabric also
             yields Kusto / ARM / Power BI tokens without re-prompting).
          2. Otherwise — or if the silent refresh fails (expired/revoked) — run
             the device-code flow, requesting `offline_access` so the response
             carries a refresh token for subsequent scopes.

        Refresh tokens rotate: every response that includes a new one replaces
        the stored token.
    .PARAMETER ClientId
        The public client id used for the device-code / refresh-token grants.
    .PARAMETER Scope
        Resource scope, e.g. 'https://api.fabric.microsoft.com/.default'.
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory)] [string] $ClientId,
        [Parameter(Mandatory)] [string] $Scope
    )

    # The raw device-code endpoint needs a concrete tenant: the tenant-less
    # 'organizations'/'common' authorities are rejected with AADSTS50059.
    # Connect-FDAObservability resolves a tenant before any token is fetched.
    $tenant = $script:FDAState.TenantId
    if (-not $tenant) {
        throw 'UserDelegated sign-in requires a tenant. Pass -TenantId, or supply a tenant ID/domain when prompted.'
    }
    $tokenUrl = "https://login.microsoftonline.com/$tenant/oauth2/v2.0/token"
    # offline_access asks AAD to return a refresh token alongside the access token.
    $scopeWithOffline = "$Scope offline_access"

    # 1. Silent refresh from a prior sign-in, if we have a refresh token.
    if ($script:FDAState.RefreshToken) {
        $refreshForm = @{
            client_id     = $ClientId
            grant_type    = 'refresh_token'
            refresh_token = $script:FDAState.RefreshToken
            scope         = $scopeWithOffline
        }
        try {
            $resp = Invoke-RestMethod -Method Post -Uri $tokenUrl -Body $refreshForm -ErrorAction Stop
            Set-FDARefreshToken -Response $resp
            Write-Verbose "Acquired token for '$Scope' via silent refresh."
            return [pscustomobject]@{
                Token     = $resp.access_token
                ExpiresOn = (Get-Date).AddSeconds([int]$resp.expires_in)
            }
        } catch {
            Write-Verbose "Silent refresh failed for '$Scope'; falling back to device code: $($_.Exception.Message)"
        }
    }

    # 2. Interactive device-code flow (first sign-in, or refresh token expired).
    $deviceUrl = "https://login.microsoftonline.com/$tenant/oauth2/v2.0/devicecode"
    $dc = Invoke-RestMethod -Method Post -Uri $deviceUrl -Body @{ client_id = $ClientId; scope = $scopeWithOffline } -ErrorAction Stop
    Write-Host ''
    Write-Host '====================================================================='
    Write-Host 'Open a browser to:' -ForegroundColor Yellow
    Write-Host " $($dc.verification_uri)" -ForegroundColor Cyan
    Write-Host 'And enter code:' -ForegroundColor Yellow
    Write-Host " $($dc.user_code)" -ForegroundColor Cyan
    Write-Host '====================================================================='
    Write-Host ''
    $deadline = (Get-Date).AddSeconds([int]$dc.expires_in)
    while ((Get-Date) -lt $deadline) {
        Start-Sleep -Seconds ([int]$dc.interval)
        $tokenForm = @{
            client_id   = $ClientId
            grant_type  = 'urn:ietf:params:oauth:grant-type:device_code'
            device_code = $dc.device_code
        }
        try {
            $resp = Invoke-RestMethod -Method Post -Uri $tokenUrl -Body $tokenForm -ErrorAction Stop
            Set-FDARefreshToken -Response $resp
            return [pscustomobject]@{
                Token     = $resp.access_token
                ExpiresOn = (Get-Date).AddSeconds([int]$resp.expires_in)
            }
        } catch {
            $err = $_.ErrorDetails.Message | ConvertFrom-Json -ErrorAction SilentlyContinue
            if ($err -and $err.error -eq 'authorization_pending') { continue }
            throw
        }
    }
    throw 'Device code flow timed out before user completed sign-in.'
}

function Set-FDARefreshToken {
    <#
    .SYNOPSIS
        Persist a rotated refresh token from a token response into module state,
        if one was returned. Refresh tokens rotate on each redemption.
    #>

    [CmdletBinding()]
    param([Parameter(Mandatory)] $Response)
    if (($Response.PSObject.Properties.Name -contains 'refresh_token') -and $Response.refresh_token) {
        $script:FDAState.RefreshToken = $Response.refresh_token
    }
}