Public/Get-M365GraphToken.ps1
|
function Get-M365GraphToken { <# .SYNOPSIS Acquires a Microsoft Graph API access token using client credentials. .DESCRIPTION Supports two authentication methods: - Client Secret: Pass -ClientSecret - Certificate: Pass -Certificate (X509Certificate2 with private key) or -CertificateThumbprint (loads from CurrentUser\My store) .OUTPUTS [string] Bearer access token #> [CmdletBinding(DefaultParameterSetName = 'ClientSecret')] param( [Parameter(Mandatory)] [string]$TenantId, [Parameter(Mandatory)] [string]$AppId, [Parameter(Mandatory, ParameterSetName = 'ClientSecret')] [string]$ClientSecret, [Parameter(Mandatory, ParameterSetName = 'Certificate')] [System.Security.Cryptography.X509Certificates.X509Certificate2]$Certificate, [Parameter(Mandatory, ParameterSetName = 'CertificateThumbprint')] [string]$CertificateThumbprint ) Write-M365Log "Authenticating to Microsoft Graph..." Write-M365Log " Tenant ID: $TenantId" Write-M365Log " Application ID: $AppId" $tokenEndpoint = "https://login.microsoftonline.com/$TenantId/oauth2/v2.0/token" if ($PSCmdlet.ParameterSetName -eq 'ClientSecret') { Write-M365Log " Auth method: Client Secret" $body = @{ client_id = $AppId scope = 'https://graph.microsoft.com/.default' client_secret = $ClientSecret grant_type = 'client_credentials' } $tokenResponse = Invoke-RestMethod -Uri $tokenEndpoint -Method Post -Body $body -ContentType 'application/x-www-form-urlencoded' } else { # Certificate auth — build JWT client assertion if ($PSCmdlet.ParameterSetName -eq 'CertificateThumbprint') { Write-M365Log " Auth method: Certificate (thumbprint: $CertificateThumbprint)" $Certificate = Get-ChildItem -Path "Cert:\CurrentUser\My\$CertificateThumbprint" -ErrorAction Stop } else { Write-M365Log " Auth method: Certificate (subject: $($Certificate.Subject))" } if (-not $Certificate.HasPrivateKey) { throw "Certificate does not contain a private key." } # x5t header: Base64url-encoded SHA-1 thumbprint $thumbprintBytes = [byte[]]::new($Certificate.Thumbprint.Length / 2) for ($i = 0; $i -lt $thumbprintBytes.Length; $i++) { $thumbprintBytes[$i] = [Convert]::ToByte($Certificate.Thumbprint.Substring($i * 2, 2), 16) } $x5t = [Convert]::ToBase64String($thumbprintBytes) -replace '\+', '-' -replace '/', '_' -replace '=' $header = @{ alg = 'RS256'; typ = 'JWT'; x5t = $x5t } | ConvertTo-Json -Compress $now = [DateTimeOffset]::UtcNow $payload = @{ aud = $tokenEndpoint iss = $AppId sub = $AppId jti = [Guid]::NewGuid().ToString() nbf = $now.ToUnixTimeSeconds() exp = $now.AddMinutes(10).ToUnixTimeSeconds() } | ConvertTo-Json -Compress $toBase64Url = { param([string]$text) $bytes = [System.Text.Encoding]::UTF8.GetBytes($text) [Convert]::ToBase64String($bytes) -replace '\+', '-' -replace '/', '_' -replace '=' } $headerB64 = & $toBase64Url $header $payloadB64 = & $toBase64Url $payload $unsigned = "$headerB64.$payloadB64" $rsa = [System.Security.Cryptography.X509Certificates.RSACertificateExtensions]::GetRSAPrivateKey($Certificate) $signatureBytes = $rsa.SignData( [System.Text.Encoding]::UTF8.GetBytes($unsigned), [System.Security.Cryptography.HashAlgorithmName]::SHA256, [System.Security.Cryptography.RSASignaturePadding]::Pkcs1 ) $signatureB64 = [Convert]::ToBase64String($signatureBytes) -replace '\+', '-' -replace '/', '_' -replace '=' $body = @{ client_id = $AppId scope = 'https://graph.microsoft.com/.default' client_assertion_type = 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer' client_assertion = "$unsigned.$signatureB64" grant_type = 'client_credentials' } $tokenResponse = Invoke-RestMethod -Uri $tokenEndpoint -Method Post -Body $body -ContentType 'application/x-www-form-urlencoded' } Write-M365Log "Successfully acquired Graph access token" return $tokenResponse.access_token } |