Token mit Zertifikat anfordern
Mein PowerShell-Script sollte sich von Entra ID ein OAUTH-Token abholen und dabei eine Anmeldung mit Zertifikat nutzen. Mit Modulen wie Get-MSALToken, Connect-MGGraph, Connect-ExchangeOnline gibt es die entsprechende Option sich mit einer AppID und ClientSecret oder auch ClientCertificate anzumelden. Ich wollte aber mal sehen, wie das manuell geht
Get-MSALToken und "Graph"
Wann immer wie mit Token arbeiten, sollten sie erst einmal nach entsprechenden Bibliotheken suchen. Wenn Sie nur eine AppID, eine TenantID und ein Client Secret haben, dann können Sie mit einem Invoke-Webrequest direkt Token als App anfordern.
param (
$LoginUrl = "https://login.Microsoftonline.com",
$TenantID = "604d9047-44e5-443a-ad8f-98abe5748b0a", #msxfaqdev
$ClientID = "05b10bad-2cac-4268-93cd-cbb5e7dbc46d", #msxfaqdev
#$Scopes = "api://ef59f0c2-12cc-43d7-aeee-d33eb3f03a8f/.default",
$Scopes = "https://graph.microsoft.com/.default",
$ClientSicret = "geheim"
)
$authresponse=Invoke-RestMethod `
-body "client_id=$($ClientID)
&scope=$scopehttps%3A%2F%2Fgraph.Microsoft.com%2F.default
&client_secret=$($ClientSecret)
&grant_type=client_credentials
&api-version=1.0" `
-uri "$($LoginUrl)/$($tenantID)/oauth2/v2.0/token"
$accesstoken= $authresponse.access_token
Natürlich haben Sie dann erst mal nur den String, den Sie so auch an die API übergeben können. Allerdings müssen Sie sich schon selbst um eine Decodierung kümmern, um das Token rechtzeitig neu anzufordern. Das funktioniert mit einem ClientSecret, also einem Kennwort, problemlos.
Sobald sie aber mit einem Client Zertifikat arbeiten wollen, müssen Sie nach meinem Verständnis einen JSON-Request erzeugen und mit dem Zertifikat digital signieren. Das zeige ich im folgenden Abschnitt. Ich weise aber auch darauf hin, dass es viel einfacher mit dem MSAL.PS-Modul möglich ist. Hier wird einfach nur der Thumbprint mit angegeben und das Modul kümmert sich um den Rest
Get-MSALToken ` -TenantId "604d9047-44e5-443a-ad8f-98abe5748b0a" ` -ClientId "05b10bad-2cac-4268-93cd-cbb5e7dbc46d" ` -ClientCertificate $certtumbprint
Hinweis:
Wenn Sie diese Codes in einem Skript mit dem Taskplaner
verwenden, dann muss das Dienstkonto oder "SYSTEM" die
Berechtigungen auf den privaten Schlüssel bekommen. Das
können Sie der Zertifikatsverwaltung machen oder Sie starten
das Skript interaktiv als Administrator oder mit "High
Priviledges".
Zertifikat ist nicht Kennwort
Wenn sich eine Applikation mit einem Zertifikat anmeldet, dann erstellen Sie in der Regel in der Umgebung ein "Self Signed Cert", welches möglichst nicht exportierbar ist und gerne auch mit der VM oder einen TPM-Chip verbunden ist. Der öffentliche Schlüssel wird als CER-Datei dann im Azure-Portal zur Applikation "hochgeladen.
Hinweis:
Gerade die KI liefert hier sehr oft Code-Beispiele, die
einfach nur die gleiche CER-Datei in den Request als
BASE64-codiertes "Kennwort" übertragen. Das kann natürlich
nicht funktionieren
Bei einer Anmeldung mittels Zertifikat wird, vereinfach ausgedrückt, über die Daten der Anforderung über TenantID, ClientID, Scope etc. ein Hashwert erstellt und dieser mit dem privaten Schlüssel verschlüsselt. Entra ID nutzt dann den öffentlichen Schlüssel aus der mitgelieferten ClientID des Tenants, um diese verschlüsselte Information wieder lesbar zu machen und mit dem Hashwert zu vergleichen. Nur der Inhaber des Zertifikats mit dem privaten Schlüssel kann diese Signatur korrekt aufbringen.
Das bedeutet natürlich auch, dass ein selbst geschriebener PowerShell-Code sich auch um die Signierung kümmern muss. Das Skript ist absichtlich "kurz" gehalten und hat keine Fehlerbehandlung, damit es kurz und lesbar bleibt. In einem produktiven Betrieb sollten Sie schon Fehlerabfragen und Diagnose-Funktionen ergänzen.
Die ganzen "Geheimnisse" sind nicht kritisch. Die TenantID zu einem Tenant ist eh öffentlich und die ClientID ist quasi nur der Username. Selbst der Thumbprint ist nur ein Verweis auf das lokale Zertifikat. Ohne den privaten Schlüssel, der hier im Code nicht sichtbar ist, können Sie diese Rechte nicht ausnutzen. Insofern sind Zertifikat sehr gut zur Authentifizierung geeignet, weil im Code damit keine "Kennworte" landen.
Wenn Sie noch kein passendes Zertifikat haben, dann können Sie dies sehr einfach wie folgt erstellen:
# Erstellen eines SelfSigned Cert mit 99 Jahren $selfcert= New-SelfSignedCertificate ` -DNSName "MSXFAQ.AppmitCert" ` -CertStoreLocation Cert:\CurrentUser\My ` -KeyExportPolicy NonExportable ` -NotAfter (Get-Date).AddYears(99) # Export des öffentlichen Teils als CER-Datei Export-Certificate ` -Cert $selfcert ` -Type CER ` -FilePath .\MSXFAQ.AppmitCert.cer
Die CER-Datei laden Sie als "Client Certificate" in die Entra ID Application und den Thumbprint brauchen Sie, genau wie die TenantID und AppID im folgenden Skript:
Param (
[string]$TenantID = "604d9047-44e5-443a-ad8f-98abe5748b0a",
[string]$ClientID = "a1f5b977-41f0-44d0-acad-48f43a8410b6",
[string]$Scopes = "api://fac5439f0c2-12cc-ef31-affe-d335427a8f/.default",
[string]$CertThumbprint = "9FAAF5C5AD2A5661CDE7BDEA46FDD36BB92FF4E3"
)
Write-Host "Das Zertifikat laden und aktuelle Zeit bestimmen"
$Cert = Get-Item "Cert:\CurrentUser\My\$CertThumbprint"
$now = [DateTimeOffset]::UtcNow.ToUnixTimeSeconds()
Write-Host "JSON-Struktur aufbauen"
$header = @{
alg = "RS256"
typ = "JWT"
x5t = [System.Convert]::ToBase64String($cert.GetCertHash())
}
$payload = @{
iss = $clientId
sub = $clientId
aud = "https://login.microsoftonline.com/$TenantID/oauth2/token"
jti = [Guid]::NewGuid().ToString()
nbf = $now
exp = $now + 600
}
Write-Host "Header und Payload in komprimiertes JSON umsetzten"
$headerJson = ($header | ConvertTo-Json -Compress)
$payloadJson = ($payload | ConvertTo-Json -Compress)
Write-Host "Header und Payload BASE64 codieren und zusammensetzen"
$jwtUnsigned = "{0}.{1}" -f `
[Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes($headerJson)).TrimEnd("="),
[Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes($payloadJson)).TrimEnd("=")
Write-Host "Signatur über den BASE64-String erstellen"
$signature = $cert.PrivateKey.SignData(
[Text.Encoding]::UTF8.GetBytes($jwtUnsigned),
[Security.Cryptography.HashAlgorithmName]::SHA256,
[Security.Cryptography.RSASignaturePadding]::Pkcs1
)
Write-Host "Signatur anhängen"
$jwtSigned = "$jwtUnsigned." + [Convert]::ToBase64String($signature).TrimEnd("=")
Write-Host "Payload für den Request erstellen"
$body = @{
grant_type = "client_credentials"
client_id = $clientId
scope = $Scopes
client_assertion_type = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"
client_assertion = $jwtSigned
}
Write-Host "Request an Entra ID stellen"
$uri = "https://login.microsoftonline.com/$($TenantID)/oauth2/token?api-version=1.0"
Write-Host " OAUTH URL: $($uri)"
$res = Invoke-WebRequest `
-Method Post `
-Uri $uri `
-Body $body
Write-Host "Ausgabe des Token"
$res
Die Variable "$Res" enthält die Ausgabe des HTLM-Request:

Interessant ist hier der "Content", der eine JSON-Struktur mit dem Bearer Token darstellt.

Ich habe daher den Aufruf auf "Invoke-RestMethod" umgestellt, so dass ich die JSON-Rückgabe gleich als PowerShell-Objekt habe:

Das Access-Token ist wieder die BASE64-codiert JSON-Struktur mit allen Informationen, welches sie einfach parsen können. Details finden Sie dazu auf Bearer Decoding und ConvertFrom-Bearertoken. Diese Token können Sie nun bei der weiteren Authentifizierung an den gewünschten Diensten nutzen.
Hinweis:
Der Inhalt von "expires_in" gibt die Gültigkeitsdauer
basierend auf dem Abrufzeitpunkt an. Die Zahl wird nicht
herunter gezählt o.ä. Ihre Software sollte daher das "exp"-Feld
des decodierten Tokens nutzen oder sich selbst merken, wann
das Token abgerufen wurde und damit ungültig wird. Es ist
nicht effektiv, in sehr engen Abständen einfach auf Verdacht
immer wieder ein neues Token abzufordern. Dann ist es
vielleicht besser, später einen HTTP-Fehler abzufangen, wenn
man ein abgelaufenes Token weiter sendet. Ideal wäre
natürlich ein Hintergrundprozess, der sich eigenständig um
die Verlängerung bzw. Erneuerung der verschiedenen Tokens
(Identity, Access, Refresh) kümmert.
Weitere Links
- Graph Token
- Bearer Decoding
- ConvertFrom-Bearertoken
- Use Get-MSALtoken to Connect to MS Graph — LazyAdmin
https://lazyadmin.nl/powershell/get-msaltoken/















