Public/Google/Get-GoogleApiAccessToken.ps1

function Get-GoogleApiAccessToken {
    <#
    .SYNOPSIS
    Retrieves an access token from Google API using a service account.
 
    .DESCRIPTION
    This function generates a JWT (JSON Web Token) and exchanges it for an access token from Google API.
    The access token can then be used for authenticated requests to Google services.
 
    The token is issued to the service account ITSELF (no user impersonation / domain-wide
    delegation). Authorization for Admin SDK calls comes from the Google Workspace admin
    role assigned to the service account (created by Initialize-IDBridgeGoogleServiceAccount);
    Sheets access comes from sharing the sheet with the service account's email.
 
    .PARAMETER ServiceAccountKeyPath
    PAth to a JSON file containing the service account credentials, including the private key and client email.
    This is the Google credential file downloaded from the Google Cloud Console.
 
    .PARAMETER ServiceAccountKeyJson
    The service account credential JSON as a string instead of a file path — e.g. read from
    the IDBridge secret vault (Get-IDBridgeSecret -Name 'GoogleAuth-ServiceAccount' -AsPlainText).
 
    .PARAMETER Scope
    The scope of access requested from Google API (e.g., 'https://www.googleapis.com/auth/drive').
 
    .EXAMPLE
    $credentialsJson = Get-Content 'C:\path\to\credentials.json' -Raw
    $accessToken = Get-GoogleApiAccessToken -ServiceAccountKeyPath $credentialsJson -Scope 'https://www.googleapis.com/auth/drive.readonly'
 
    Use the $accessToken for authenticated API requests.
 
    .NOTES
    Ensure that the service account has the necessary permissions for the requested scope.
 
    Created by: Sam Cattanach
    Modified: 02/18/2025 09:30:19 AM
    #>


    [CmdletBinding(DefaultParameterSetName = 'Path')]
    param (
        [Parameter(Mandatory, ParameterSetName = 'Path')]
        [string]$ServiceAccountKeyPath,

        [Parameter(Mandatory, ParameterSetName = 'Json')]
        [string]$ServiceAccountKeyJson,

        [Parameter(Mandatory)]
        [string]$Scope
    )

    # Convert JSON credentials into a PowerShell object
    if ($PSCmdlet.ParameterSetName -eq 'Path') {
        $ServiceAccountKeyJson = Get-Content $ServiceAccountKeyPath -Raw
    }
    $jsonContent = ConvertFrom-Json -InputObject $ServiceAccountKeyJson
    $ServiceAccountEmail = $jsonContent.client_email
    $PrivateKey = $jsonContent.private_key -replace '-----BEGIN PRIVATE KEY-----\n' -replace '\n-----END PRIVATE KEY-----\n' -replace '\n'

    # Create JWT Header (Base64-encoded JSON)
    $header = @{
        alg = "RS256"
        typ = "JWT"
    }
    $headerBase64 = [System.Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes(($header | ConvertTo-Json -Compress)))

    # Generate JWT Payload (Claim Set)
    $timestamp = [Math]::Round((Get-Date -UFormat %s))  # Current time in seconds
    $claimSet = @{
        iss   = $ServiceAccountEmail
        scope = $Scope
        aud   = "https://oauth2.googleapis.com/token"
        exp   = $timestamp + 3600  # Token expiration (1 hour)
        iat   = $timestamp         # Issued at time
    }
    $claimSetBase64 = [System.Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes(($claimSet | ConvertTo-Json -Compress)))

    # Generate JWT Signature
    $signatureInput = "$headerBase64.$claimSetBase64"
    $signatureBytes = [System.Text.Encoding]::UTF8.GetBytes($signatureInput)
    $privateKeyBytes = [System.Convert]::FromBase64String($PrivateKey)

    # Create RSA provider and import the private key
    $rsaProvider = [System.Security.Cryptography.RSA]::Create()
    $bytesRead = $null
    $rsaProvider.ImportPkcs8PrivateKey($privateKeyBytes, [ref]$bytesRead)

    # Sign the JWT using SHA-256 and PKCS1 padding
    $signature = $rsaProvider.SignData($signatureBytes, [System.Security.Cryptography.HashAlgorithmName]::SHA256, [System.Security.Cryptography.RSASignaturePadding]::Pkcs1)
    $signatureBase64 = [System.Convert]::ToBase64String($signature)

    # Construct the final JWT
    $jwt = "$headerBase64.$claimSetBase64.$signatureBase64"

    # Create request body for token exchange
    $body = @{
        grant_type = "urn:ietf:params:oauth:grant-type:jwt-bearer"
        assertion  = $jwt
    }

    # Request access token from Google OAuth2 API
    try {
        $response = Invoke-RestMethod -Uri "https://oauth2.googleapis.com/token" -Method POST -Body $body -ContentType "application/x-www-form-urlencoded"

        $accessToken = $response.access_token

        $headers = @{
            'Authorization' = "Bearer $accessToken"
            'Accept' = 'application/json'
        }
        
        Write-Log -Message "Successfully retrieved Google Access Token" -Level Trace

        return $headers
    } catch {
        Write-Log -Message "Failed to Retrieve Google Access Token" -Level Error
        Throw $_
    }
}