Private/Runtime/Get-OpenApiOAuthToken.ps1

function Get-OpenApiOAuthToken {
    <#
    .SYNOPSIS
        Returns an OAuth2 client-credentials access token for a context, from the cache or from the token endpoint (cached until 60 s before expiry).
    #>

    [CmdletBinding()]
    [OutputType([System.Security.SecureString])]
    param(
        [Parameter(Mandatory)]
        [pscustomobject]$Context,

        [Parameter(Mandatory)]
        [System.Net.Http.HttpClient]$Client,

        [Parameter(Mandatory)]
        [string]$TokenUri,

        [Parameter()]
        [AllowNull()]
        [AllowEmptyCollection()]
        [string[]]$Scope,

        [Parameter()]
        [switch]$Force
    )

    $cache = Get-OpenApiModuleState -Name 'TcsOpenApiTokenCache'
    $scopeText = (@($Scope | Where-Object -FilterScript { -not [string]::IsNullOrEmpty($_) }) -join ' ')
    $key = '{0}|{1}|{2}|{3}' -f $Context.Service, $TokenUri, $Context.ClientId, $scopeText
    $entry = $cache[$key]
    if (-not $Force -and $null -ne $entry -and $entry.ExpiresAt -gt [datetime]::UtcNow) {
        return $entry.Token
    }

    $form = [ordered]@{
        grant_type    = 'client_credentials'
        client_id     = $Context.ClientId
        client_secret = ConvertFrom-OpenApiSecureString -SecureString $Context.ClientSecret
    }
    if ($scopeText.Length -gt 0) {
        $form['scope'] = $scopeText
    }
    $bodyText = ConvertTo-OpenApiFormBody -InputObject $form
    $form = $null
    $request = New-Object System.Net.Http.HttpRequestMessage -ArgumentList ([System.Net.Http.HttpMethod]::Post), ([uri]$TokenUri)
    $utf8 = New-Object System.Text.UTF8Encoding -ArgumentList $false
    $request.Content = New-Object System.Net.Http.ByteArrayContent -ArgumentList (, $utf8.GetBytes($bodyText))
    $request.Content.Headers.ContentType = [System.Net.Http.Headers.MediaTypeHeaderValue]::Parse('application/x-www-form-urlencoded')
    [void]$request.Headers.TryAddWithoutValidation('Accept', 'application/json')
    Write-Verbose -Message "POST $TokenUri (OAuth2 client credentials token request)"
    Write-Debug -Message ("Token request body: " + (Get-OpenApiRedactedText -Text $bodyText))
    $bodyText = $null
    try {
        try {
            $response = $Client.SendAsync($request).GetAwaiter().GetResult()
        }
        catch {
            $inner = $_.Exception
            while ($inner -is [System.Management.Automation.MethodInvocationException] -and $null -ne $inner.InnerException) {
                $inner = $inner.InnerException
            }
            throw (New-Object System.Security.Authentication.AuthenticationException -ArgumentList "The OAuth2 token request to $TokenUri failed: $($inner.Message)", $inner)
        }
        try {
            $text = $response.Content.ReadAsStringAsync().GetAwaiter().GetResult()
            $status = [int]$response.StatusCode
            Write-Verbose -Message "Token response: $status $($response.ReasonPhrase)"
            if ($status -lt 200 -or $status -gt 299) {
                $detail = $response.ReasonPhrase
                try {
                    $problem = $text | ConvertFrom-Json -ErrorAction Stop
                    if ($problem.error_description) {
                        $detail = "$($problem.error): $($problem.error_description)"
                    }
                    elseif ($problem.error) {
                        $detail = [string]$problem.error
                    }
                }
                catch {
                    Write-Debug -Message 'The token error response is not JSON.'
                }
                throw (New-Object System.Security.Authentication.AuthenticationException -ArgumentList "The OAuth2 token request to $TokenUri failed with $status ($detail).")
            }
            $token = $text | ConvertFrom-Json -ErrorAction Stop
        }
        finally {
            $response.Dispose()
        }
    }
    finally {
        $request.Dispose()
    }
    if ([string]::IsNullOrEmpty($token.access_token)) {
        throw (New-Object System.Security.Authentication.AuthenticationException -ArgumentList "The OAuth2 token response from $TokenUri has no access_token.")
    }
    $lifetime = 3600
    if ($null -ne $token.expires_in) {
        $lifetime = [double]$token.expires_in
    }
    $secure = New-Object System.Security.SecureString
    foreach ($character in ([string]$token.access_token).ToCharArray()) {
        $secure.AppendChar($character)
    }
    $secure.MakeReadOnly()
    $cache[$key] = [pscustomobject]@{
        Token     = $secure
        ExpiresAt = [datetime]::UtcNow.AddSeconds($lifetime - 60)
    }
    return $secure
}