In automation scenarios it is common to use a service principal (app based) to work with the Graph API, or in my example, with PNP PowerShell against SharePoint (but both scenario’s work the same).
First, you’d need a client certificate, e.g. like this:
$folder = "c:\users\JosLieben\Desktop"
$cert=New-SelfSignedCertificate -Subject "CN=JOS" -CertStoreLocation "Cert:\CurrentUser\My" -KeyExportPolicy Exportable -KeySpec Signature -HashAlgorithm "SHA256" -Provider "Microsoft Enhanced RSA and AES Cryptographic Provider"
Export-Certificate -Cert $cert -FilePath "$folder\jos.cer"
Export-PfxCertificate -Password (ConvertTo-SecureString $clientCertPwd -AsPlainText -Force) -Cert $cert -FilePath "$folder\jos.pfx"
You’d then upload the .cer file as a certificate on your service principal to let Azure AD recognize your cert as a valid ‘password’ for your app registration.
Next you’d upload your .pfx file into Keyvault.
Finally, you can use Powershell to construct an access token for a given scope:
$tenantId = "YOURTENANTID"
$clientId = "YOURCLIENTID"
$scope = "https://graph.microsoft.com/.default" #or, e.g. https://$($tenantName)-admin.sharepoint.com/.default openid profile offline_access
$secret = Get-AzKeyVaultSecret -VaultName "YOURKEYVAULTNAME" -Name cippCert -AsPlainText
$clientCert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2 -ArgumentList @([Convert]::FromBase64String($secret),"",[System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::Exportable)
$header = @{
alg = "RS256"
typ = "JWT"
x5t = [System.Convert]::ToBase64String(($clientCert.GetCertHash()))
} | ConvertTo-Json -Compress
$claimsPayload = @{
aud = "https://login.microsoftonline.com/$tenantId/oauth2/token"
exp = [math]::Round(((New-TimeSpan -Start ((Get-Date "1970-01-01T00:00:00Z" ).ToUniversalTime()) -End (Get-Date).ToUniversalTime().AddMinutes(2)).TotalSeconds),0)
iss = $clientId
jti = (New-Guid).Guid
nbf = [math]::Round(((New-TimeSpan -Start ((Get-Date "1970-01-01T00:00:00Z" ).ToUniversalTime()) -End ((Get-Date).ToUniversalTime())).TotalSeconds),0)
sub = $clientId
} | ConvertTo-Json -Compress
$headerjsonbase64 = [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($header)).Split('=')[0].Replace('+', '-').Replace('/', '_')
$claimsPayloadjsonbase64 = [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($claimsPayload)).Split('=')[0].Replace('+', '-').Replace('/', '_')
$preJwt = $headerjsonbase64 + "." + $claimsPayloadjsonbase64
$toSign = [System.Text.Encoding]::UTF8.GetBytes($preJwt)
$privateKey = $clientCert.PrivateKey
$alg = [Security.Cryptography.HashAlgorithmName]::SHA256
$padding = [Security.Cryptography.RSASignaturePadding]::Pkcs1
$signature = [Convert]::ToBase64String($privateKey.SignData($toSign,$alg,$padding)) -replace '\+','-' -replace '/','_' -replace '='
$jwt = $headerjsonbase64 + "." + $claimsPayloadjsonbase64 + "." + $signature
$Authbody = @{
'tenant' = $tenantId
'scope' = $scope
'client_assertion_type' = 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer'
'client_id' = $clientId
'grant_type' = 'client_credentials'
'client_assertion' = $jwt
}
$accessToken = (Invoke-RestMethod -Method post -Uri "https://login.microsoftonline.com/$($tenantid)/oauth2/v2.0/token" -Body $Authbody -ErrorAction Stop).accesstoken
The token can then be used to used to call Graph. And an example that shows how to use a sharepoint scoped token for the Sharepoint PNP PowerShell moddule:
#use scope: https://$($tenantName)-admin.sharepoint.com/.default
connect-PnPOnline -Url "https://$($tenantName)-admin.sharepoint.com" -AccessToken $accessToken -ReturnConnection