When using Graph or the new ExchangeOnlineManagement module we can easily connect to to the Azure AD App using a self-signed client certificate. For the Office365 O365 Management API however there are only examples connecting using a shared secret, but if you need to use a cert then the following code is used:
$ClientID = "xxxxx"
$TenantName = "tenant.onmicrosoft.com"
$AppId = "xxxxx"
$Certificate = Get-ChildItem -Path cert:\Currentuser\My | Where {$_.Thumbprint -eq $thumbprint}
$Scope = "https://graph.microsoft.com/.default"
$loginURL = "https://login.microsoftonline.com/"
$CertificateBase64Hash = [System.Convert]::ToBase64String($Certificate.GetCertHash())
$StartDate = (Get-Date "1970-01-01T00:00:00Z" ).ToUniversalTime()
$JWTExpirationTimeSpan = (New-TimeSpan -Start $StartDate -End (Get-Date).ToUniversalTime().AddMinutes(2)).TotalSeconds
$JWTExpiration = [math]::Round($JWTExpirationTimeSpan,0)
$NotBeforeExpirationTimeSpan = (New-TimeSpan -Start $StartDate -End ((Get-Date).ToUniversalTime())).TotalSeconds
$NotBefore = [math]::Round($NotBeforeExpirationTimeSpan,0)
$JWTHeader = @{
alg = "RS256"
typ = "JWT"
# Use the CertificateBase64Hash and replace/strip to match web encoding of base64
x5t = $CertificateBase64Hash -replace '\+','-' -replace '/','_' -replace '='
}
$JWTPayLoad = @{
aud = "https://login.microsoftonline.com/$TenantName/oauth2/token"
exp = $JWTExpiration
iss = $AppId
jti = [guid]::NewGuid()
nbf = $NotBefore
sub = $AppId
}
$JWTHeaderToByte = [System.Text.Encoding]::UTF8.GetBytes(($JWTHeader | ConvertTo-Json))
$EncodedHeader = [System.Convert]::ToBase64String($JWTHeaderToByte)
$JWTPayLoadToByte = [System.Text.Encoding]::UTF8.GetBytes(($JWTPayload | ConvertTo-Json))
$EncodedPayload = [System.Convert]::ToBase64String($JWTPayLoadToByte)
$JWT = $EncodedHeader + "." + $EncodedPayload
$PrivateKey = $Certificate.PrivateKey
$RSAPadding = [Security.Cryptography.RSASignaturePadding]::Pkcs1
$HashAlgorithm = [Security.Cryptography.HashAlgorithmName]::SHA256
$Signature = [Convert]::ToBase64String(
$PrivateKey.SignData([System.Text.Encoding]::UTF8.GetBytes($JWT),$HashAlgorithm,$RSAPadding)
) -replace '\+','-' -replace '/','_' -replace '='
# Join the signature to the JWT with "."
$JWT = $JWT + "." + $Signature
$resource = "https://manage.office.com"
$body = @{grant_type="client_credentials";resource=$resource;client_id=$ClientID;client_assertion=$JWT;client_assertion_type="urn:ietf:params:oauth:client-assertion-type:jwt-bearer"}
$oauth = Invoke-RestMethod -Method Post -Uri $loginURL/$tenantname/oauth2/token?api-version=1.0 -Body $body
$headerParams = @{'Authorization'="$($oauth.token_type) $($oauth.access_token)"}
$oauth.access_token
$TenantName = "tenant.onmicrosoft.com"
$AppId = "xxxxx"
$Certificate = Get-ChildItem -Path cert:\Currentuser\My | Where {$_.Thumbprint -eq $thumbprint}
$Scope = "https://graph.microsoft.com/.default"
$loginURL = "https://login.microsoftonline.com/"
$CertificateBase64Hash = [System.Convert]::ToBase64String($Certificate.GetCertHash())
$StartDate = (Get-Date "1970-01-01T00:00:00Z" ).ToUniversalTime()
$JWTExpirationTimeSpan = (New-TimeSpan -Start $StartDate -End (Get-Date).ToUniversalTime().AddMinutes(2)).TotalSeconds
$JWTExpiration = [math]::Round($JWTExpirationTimeSpan,0)
$NotBeforeExpirationTimeSpan = (New-TimeSpan -Start $StartDate -End ((Get-Date).ToUniversalTime())).TotalSeconds
$NotBefore = [math]::Round($NotBeforeExpirationTimeSpan,0)
$JWTHeader = @{
alg = "RS256"
typ = "JWT"
# Use the CertificateBase64Hash and replace/strip to match web encoding of base64
x5t = $CertificateBase64Hash -replace '\+','-' -replace '/','_' -replace '='
}
$JWTPayLoad = @{
aud = "https://login.microsoftonline.com/$TenantName/oauth2/token"
exp = $JWTExpiration
iss = $AppId
jti = [guid]::NewGuid()
nbf = $NotBefore
sub = $AppId
}
$JWTHeaderToByte = [System.Text.Encoding]::UTF8.GetBytes(($JWTHeader | ConvertTo-Json))
$EncodedHeader = [System.Convert]::ToBase64String($JWTHeaderToByte)
$JWTPayLoadToByte = [System.Text.Encoding]::UTF8.GetBytes(($JWTPayload | ConvertTo-Json))
$EncodedPayload = [System.Convert]::ToBase64String($JWTPayLoadToByte)
$JWT = $EncodedHeader + "." + $EncodedPayload
$PrivateKey = $Certificate.PrivateKey
$RSAPadding = [Security.Cryptography.RSASignaturePadding]::Pkcs1
$HashAlgorithm = [Security.Cryptography.HashAlgorithmName]::SHA256
$Signature = [Convert]::ToBase64String(
$PrivateKey.SignData([System.Text.Encoding]::UTF8.GetBytes($JWT),$HashAlgorithm,$RSAPadding)
) -replace '\+','-' -replace '/','_' -replace '='
# Join the signature to the JWT with "."
$JWT = $JWT + "." + $Signature
$resource = "https://manage.office.com"
$body = @{grant_type="client_credentials";resource=$resource;client_id=$ClientID;client_assertion=$JWT;client_assertion_type="urn:ietf:params:oauth:client-assertion-type:jwt-bearer"}
$oauth = Invoke-RestMethod -Method Post -Uri $loginURL/$tenantname/oauth2/token?api-version=1.0 -Body $body
$headerParams = @{'Authorization'="$($oauth.token_type) $($oauth.access_token)"}
$oauth.access_token
The O365 Management API is still needed if you want to audit admin changes.