Private/Get-EasyGraphAuthTokenCert.ps1

function Get-EasyGraphAuthTokenCert {

    param(
        [Parameter(ParameterSetName='CertStore',Mandatory=$true)]
        [switch]$CertStore,

        [Parameter(ParameterSetName='Pfx',Mandatory=$true)]
        [switch]$Pfx
    )

    if ($CertStore) {
        if ($IsLinux -or $IsMacOS) {
            throw 'The selected authentication method is not available on this platform'
        }
        $Certificate = Get-ChildItem -Path "cert:\*\My\$($GraphConnection.CertificateThumbprint)" -Recurse | Select-Object -First 1
    }

    if ($Pfx) {
        $Certificate = New-Object -TypeName 'System.Security.Cryptography.X509Certificates.X509Certificate2Collection'
        $Certificate.Import($GraphConnection.PfxFilePath,($GraphConnection.PfxPassword | ConvertFrom-SecureStringAsPlainText),[System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::PersistKeySet)
        $Certificate = $Certificate | Select-Object -Last 1 # If there are more certificates in the chain
    }

    if (-not $Certificate) {
        throw 'Certificate could not be loaded'
    }

    if ($Certificate.HasPrivateKey -and -not $Certificate.PrivateKey) {
        throw 'Could not access the certificate private key'
    }

    $UTCNow = [int]([DateTime]::UtcNow - (New-Object -TypeName DateTime 1970,1,1,0,0,0,([DateTimeKind]::Utc))).TotalSeconds

    $JWTHeader = @{
        alg = 'RS256'
        typ = 'JWT'
        x5t = [System.Convert]::ToBase64String($Certificate.GetCertHash()) -replace '\+','-' -replace '/','_' -replace '='
    }

    $JWTPayLoad = @{
        aud = "https://login.microsoftonline.com/$($GraphConnection.TenantId)/oauth2/token"
        exp = $UTCNow + 120
        iss = $GraphConnection.AppId
        jti = [guid]::NewGuid()
        nbf = $UTCNow
        sub = $GraphConnection.AppId
    }

    $EncodedHeader = [System.Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes(($JWTHeader | ConvertTo-Json)))
    $EncodedPayload = [System.Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes(($JWTPayload | ConvertTo-Json)))

    $JWT = $EncodedHeader + '.' + $EncodedPayload

    $RSAPadding = [Security.Cryptography.RSASignaturePadding]::Pkcs1
    $HashAlgorithm = [Security.Cryptography.HashAlgorithmName]::SHA256

    $Signature = [Convert]::ToBase64String(
        $Certificate.PrivateKey.SignData([System.Text.Encoding]::UTF8.GetBytes($JWT),$HashAlgorithm,$RSAPadding)
    ) -replace '\+','-' -replace '/','_' -replace '='

    $JWT = $JWT + "." + $Signature

    $TokenRequest = @{
        ContentType = 'application/x-www-form-urlencoded'
        Method = 'POST'
        Body = @{
            client_id = $GraphConnection.AppId
            client_assertion = $JWT
            client_assertion_type = 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer'
            scope = 'https://graph.microsoft.com/.default'
            grant_type = 'client_credentials'
        }
        Uri = "https://login.microsoftonline.com/$($GraphConnection.TenantId)/oauth2/v2.0/token"
        Headers = @{Authorization = "Bearer $JWT"}
    }

    $TokenResponse = Invoke-RestMethod @TokenRequest

    $GraphConnection.AccessToken = $TokenResponse.access_token
    $GraphConnection.Expires = ([DateTime]::UtcNow).AddSeconds($TokenResponse.expires_in)
}