{"id":518,"date":"2024-01-28T15:11:04","date_gmt":"2024-01-28T14:11:04","guid":{"rendered":"https:\/\/sparrow365.de\/?p=518"},"modified":"2024-11-04T20:21:32","modified_gmt":"2024-11-04T19:21:32","slug":"connect-mggraph-mit-benutzername-und-passwort","status":"publish","type":"post","link":"https:\/\/sparrow365.de\/index.php\/2024\/01\/28\/connect-mggraph-mit-benutzername-und-passwort\/","title":{"rendered":"Connect-MgGraph mit Benutzername und Passwort"},"content":{"rendered":"<p>In meiner Arbeit an der <a href=\"https:\/\/sparrow365.de\/wp-content\/uploads\/2024\/01\/ComingSoon.png\">praktischen Umsetzung<\/a> von <a href=\"https:\/\/sparrow365.de\/index.php\/2023\/12\/31\/theater-gegen-overprivilege-pam-edition-theorie\/\">Kennwortrotation ohne Privileged Authentication Administrator<\/a>, bin ich \u00fcber eine etwas <strong>umfangreiche Herausforderung<\/strong> gestolpert.<\/p>\n<p>Wenn man sich \u00fcber PowerShell mit <strong>Benutzername + Passwort<\/strong> an der Graph API anmelden will, findet man <a href=\"https:\/\/learn.microsoft.com\/en-us\/powershell\/module\/microsoft.graph.authentication\/connect-mggraph?view=graph-powershell-1.0\">keine Kombination in der PowerShell SDK<\/a>.<br \/>\nDie einzige Methode w\u00e4re ClientID + Secret &#8211; damit w\u00e4ren wir aber wieder bei der Nutzung von App Permissions, was ich in diesem Fall konkret vermeiden will.<\/p>\n<p>Wir wissen auch, dass <em>es geht<\/em>, weil in der <strong>Azure command line<\/strong> <a href=\"https:\/\/learn.microsoft.com\/en-us\/cli\/azure\/authenticate-azure-cli-interactively#sign-in-with-credentials-on-the-command-line\">ein solcher connect m\u00f6glich<\/a> ist &#8211; <strong>nur daf\u00fcr<\/strong> aber das Az Modul oder die Azure CLI zu fordern <strong>widerspricht meinem Perfektionismus<\/strong>. Vor allem weil es dort auch nicht trivial ist, sich gegen eine spezifische App Registration zu authentifizieren.<\/p>\n<blockquote>\n<p><em><a href=\"https:\/\/winsmarts.com\/super-easy-way-to-get-an-access-token-ddb9e56bcdf\">Die faule L\u00f6sung gibt es entsprechend schon<\/a> &#8211; den Token kann man dann mit <code><code>Connect-MgGraph -AccessToken $(ConvertTo-SecureString &quot;&lt;AccessToken&gt;&quot;)<\/code><\/code> weiter verwenden<\/em><\/p>\n<\/blockquote>\n<p>Da ein so spezifisches Teilproblem bald den halben Artikel einnahm, fasse ich lieber hier separat zusammen, wie ich zu <a href=\"https:\/\/gist.github.com\/dreadsend\/fb46410db717ca3e937acbc9fccca754\"><strong>meiner L\u00f6sung<\/strong><\/a> gekommen bin.<\/p>\n<p><br class=\"\"><\/p>\n<h2>Erste Untersuchung<\/h2>\n<p>Beginnend mit dem Wissen, dass es eine funktionierende Implementierung gibt, <a href=\"https:\/\/chat.openai.com\/share\/7a45fe82-d630-41d5-b1a8-06d72caaf89c\">habe ich mir mal mehr Details geben lassen<\/a>. So komme ich also zu dem <strong>Resource Owner Password Credentials (ROPC) grant<\/strong>. <a href=\"https:\/\/learn.microsoft.com\/en-us\/entra\/identity-platform\/v2-oauth-ropc\">In der zugeh\u00f6rigen Dokumentation<\/a> finden wir auch schon sehr detaillierte Informationen, wie er zu benutzen ist.   <\/p>\n<p><u><strong>N\u00e4mlich idealerweise gar nicht:<\/strong><\/u><\/p>\n<p><img decoding=\"async\" src=\"https:\/\/sparrow365.de\/wp-content\/uploads\/2024\/01\/ROPCWarning.png\" alt=\"ROPC Warning\" \/><\/p>\n<p>Moderne Authentifizierungsprotokolle sind so gebaut, dass Benutzeranmeldung <strong>so weit von der Applikation getrennt sein soll wie m\u00f6glich<\/strong> &#8211; bei allen anderen OIDC Flows bekommen wir einfach die Best\u00e4tigung, dass der Benutzer authentifiziert ist. Hier <strong>halten wir tempor\u00e4r seine Credentials<\/strong> &#8211; wo wir es meistens gar nicht m\u00fcssten.<\/p>\n<p>Allerdings ist da die relevante Phrase: <u><em>&quot;in most scenarios&quot;<\/em><\/u>. Wenn ich mich <strong>ohne Interaktion<\/strong> <u><em>als normaler Benutzer<\/em><\/u> anmelden will, gibt es meines Wissens <strong>keine Alternative<\/strong> &#8211; ich bin aber f\u00fcr eine Korrektur offen. Der Upside ist es in meinem Szenario Wert, weil paradoxerweise in meinem ganz spezifischen Usecase <strong>Benutzerberechtigungen um Meilen granularer<\/strong> sind als Applikationsrechte &#8211; und ich eh die Passw\u00f6rter der Benutzer im PAM Vault habe.<\/p>\n<p>Wir werden aber auch sehr wahrscheinlich <strong>keine SDKs<\/strong> finden, die so funktionieren wie ich es gerne h\u00e4tte: Ich stimme Microsoft zu, ich will nicht, dass auf diese App von Benutzern zugegriffen wird. Sonst w\u00fcrden sie sich <strong>ohne MFA<\/strong> Authentifizieren k\u00f6nnen und requests gegen die API stellen. Der beste Weg dazu w\u00e4re, dass ich eine <a href=\"https:\/\/learn.microsoft.com\/en-us\/entra\/identity-platform\/msal-client-applications#when-does-proving-client-identity-matter\"><strong>confidential client App<\/strong><\/a> registriere, wo bei Requests immer ein Zertifikat mitgegeben werden muss um nachzuweisen, dass das System die App Registration <strong>nutzen darf<\/strong>. Aber: <\/p>\n<p><img decoding=\"async\" src=\"https:\/\/sparrow365.de\/wp-content\/uploads\/2024\/01\/ROPCNoConfidential.png\" alt=\"ROPC No Confidential\" \/><\/p>\n<p>Nachvollziehbar, wenn man davon ausgeht, dass <strong>beim Login<\/strong> Passw\u00f6rter an den Server weiter gegeben werden w\u00fcrden. In unserem Fall aber nicht zutreffend, weil wir die <strong>Kennw\u00f6rter ja schon haben<\/strong>.<\/p>\n<p>Wenn die SDKs es nicht unterst\u00fctzen, werden wir wohl die Web Requests nachbauen m\u00fcssen.<\/p>\n<p><br class=\"\"><\/p>\n<h2>Bau des Code<\/h2>\n<p>Ich mache mir zwar oft viel mehr Arbeit als notwendig w\u00e4re (So wie jetzt gerade? \ud83e\udd14) &#8211; ich will aber nicht ganz \u00fcbertreiben. Es gibt <strong>mehr als genug Implementierungen<\/strong> der Graph API Auth \u00fcber <code><code>Invoke-WebRequest<\/code><\/code> &#8211; ich werde mir also eine m\u00f6glichst gute Version nehmen und f\u00fcr ROPC <strong>anpassen<\/strong>.<\/p>\n<p><strong>Am besten<\/strong> sah f\u00fcr mich die Version von <a href=\"https:\/\/adamtheautomator.com\/powershell-graph-api\/\">Adam The Automator<\/a> aus &#8211; vor allem weil dort auch direkt die <strong>zertifikatsbasierte Authentifizierung<\/strong> implementiert ist, an der ich wahrscheinlich eine ganze Weile geknabbert h\u00e4tte.<\/p>\n<p>Bei ersten Tests des Codes stolpere ich aber \u00fcber in Problem:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/sparrow365.de\/wp-content\/uploads\/2024\/01\/ROPCInitialError.png\" alt=\"Error Connecting\" \/><\/p>\n<p>Nach ein wenig Troubleshooting identifiziere ich die folgende Passage:<\/p>\n<pre><code class=\"language-powershell\">#...\n$Certificate = Get-Item Cert:\\CurrentUser\\My\\$thumbprint\n#...\n\n# Get the private key object of your certificate\n$PrivateKey = $Certificate.PrivateKey\n\n# Define RSA signature and hashing algorithm\n$RSAPadding = [Security.Cryptography.RSASignaturePadding]::Pkcs1\n$HashAlgorithm = [Security.Cryptography.HashAlgorithmName]::SHA256\n\n# Create a signature of the JWT\n$Signature = [Convert]::ToBase64String(\n    $PrivateKey.SignData([System.Text.Encoding]::UTF8.GetBytes($JWT),$HashAlgorithm,$RSAPadding)\n) -replace &#039;\\+&#039;,&#039;-&#039; -replace &#039;\/&#039;,&#039;_&#039; -replace &#039;=&#039;\n#...<\/code><\/pre>\n<p>Wahrscheinlich weil meine Private Keys nicht exportierbar sind blieb <code><code>$PrivateKey<\/code><\/code> leer.<br \/>\nAuch private Keys, die nicht exportiert werden k\u00f6nnen sind aber auf dem System nutzbar, auf dem sie installiert sind &#8211; sonst w\u00fcrde <code><code>Connect-MgGraph -CertificateThumbprint -ClientId<\/code><\/code> auch fehlschlagen.<\/p>\n<p>Baut man ein .Net Objekt direkt aus dem Zertifikat, dass einem eine Signatur liefern kann, kommen wir weiter:<\/p>\n<pre><code class=\"language-powershell\">$rsaCert = [System.Security.Cryptography.X509Certificates.RSACertificateExtensions]::GetRSAPrivateKey($Certificate)\n$Signature = [Convert]::ToBase64String(\n    $rsaCert.SignData([System.Text.Encoding]::UTF8.GetBytes($JWT), $HashAlgorithm, $RSAPadding)\n) -replace &#039;\\+&#039;, &#039;-&#039; -replace &#039;\/&#039;, &#039;_&#039; -replace &#039;=&#039;<\/code><\/pre>\n<p><img decoding=\"async\" src=\"https:\/\/sparrow365.de\/wp-content\/uploads\/2024\/01\/ROPCSignature-1.png\" alt=\"SuccessfulSignature\" \/><\/p>\n<p><br class=\"\"><\/p>\n<p><em>Danach folgen nur noch Anpassungen, die zwar eine Weile dauern, aber weniger interressant darzustellen sind: Ich<\/em><\/p>\n<ol>\n<li><em>\u00fcbertrage die <a href=\"https:\/\/learn.microsoft.com\/en-us\/entra\/identity-platform\/v2-oauth-ropc\">HTTP Beispiele aus der Doku<\/a> in entsprechende PowerShell Syntax<\/em><\/li>\n<li><em>benutze den Access Token, um mich mit Connect-MgGraph zu verbinden<\/em><\/li>\n<li><em>parametrisiere alles, so dass es eine wiederaufrufbare Funktion wird<\/em><\/li>\n<li><em>\u00fcbernehme auch die Authentifizierung gegen Public Clients und mit Client Secret, der Vollst\u00e4ndigkeit halber<\/em><\/li>\n<li><em>stelle sicher, dass die sensiblen Variablen geleert werden<\/em><\/li>\n<\/ol>\n<p><br class=\"\"><\/p>\n<h2>Nutzung<\/h2>\n<p><strong>Voraussetzungen:<\/strong>  <\/p>\n<ul>\n<li>Wie so \u00fcblich braucht man auch hier eine Enterprise Applikation, mit den delegate Rechten, die man sp\u00e4ter nutzen will <\/li>\n<li>Wenn keine App Credentials genutzt werden sollen, muss in den Advanced Settings die Registration als &quot;Public Client App&quot; markiert werden\n<ul>\n<li><em>Bitte dringend vermeiden<\/em><\/li>\n<\/ul>\n<\/li>\n<li>Die App muss von MFA und Compliant Device Kontrollen in Conditional Access ausgenommen werden\n<ul>\n<li>Idealerweise wird stattdessen mindestens die IP Adresse gepr\u00fcft und die zugelassenen Benutzer eingeschr\u00e4nkt<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<p><br class=\"\"><\/p>\n<p>Verbindungsaufbau ist sehr einfach:<\/p>\n<pre><code class=\"language-powershell\">$params = @{\n  #alternativ get-credentials\n  userCredentials       = $creds\n  tenantId              = $config.tenantID\n  clientId              = $config.clientID\n  scopes                = @(&quot;User.Read&quot;,&quot;Directory.AccessAsUser.All&quot;)\n  certificateThumbprint = $config.thumb\n}\nConnect-ROPCGraph @params<\/code><\/pre>\n<p>Der Aufbau ist bewusst m\u00f6glichst nah an Connect-MgGraph &#8211; wir brauchen aber nun mal die Credentials.<\/p>\n<p>Im ersten Moment bekommt man aber keinen Output &#8211; das soll aber so, die Funktion ist schlie\u00dflich f\u00fcr noninteraktive Prozesse gebaut. Wir sehen den Erfolg, wenn wir andere Kommandos aus der Graph SDK ausf\u00fchren und feststellen d\u00fcrfen, dass alles so funktioniert wie gewohnt:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/sparrow365.de\/wp-content\/uploads\/2024\/01\/ROPCSuccess.png\" alt=\"Successfull Connection\" \/><\/p>\n<p><strong>Einschr\u00e4nkungen<\/strong><br \/>\n<a href=\"https:\/\/learn.microsoft.com\/en-us\/entra\/identity-platform\/v2-oauth-ropc\">TL:DR des gro\u00dfen Warnungsblocks von ROPC<\/a>; Folgendes Funktioniert <strong>NICHT<\/strong><\/p>\n<ul>\n<li>Microsoft <strong>Personal<\/strong> Accounts<\/li>\n<li><strong>Passwortlose<\/strong> Accounts<\/li>\n<li>Logins, die <strong>MFA<\/strong> Fordern<\/li>\n<li><strong>F\u00f6derierte<\/strong> Accounts<\/li>\n<li><strong>Kennw\u00f6rter<\/strong> mit <strong>Leerzeichen<\/strong> am <strong>Anfang oder Ende<\/strong> <\/li>\n<\/ul>\n<p><br class=\"\"><\/p>\n<h2>Fazit<\/h2>\n<p>Jetzt k\u00f6nnen wir uns ohne Benutzerzutun als End User Anmelden und die zugeh\u00f6rigen Vorteile ausnutzen. Wir wissen aber, dass es einen Grund hat, dass dieser Flow nicht einfach verf\u00fcgbar gemacht wird &#8211; explizite Nutzung von Benutzerpassw\u00f6rtern und Umgehen von MFA sollte so selten wie m\u00f6glich genutzt werden.<\/p>\n<p>Und ich kann mit meinem Passwortrotations-PoC weiter machen \ud83d\ude09<\/p>\n<p><br class=\"\"><\/p>\n<p>Ich werde keine Kommentare moderieren und m\u00f6chte Ihre E-Mail-Adresse nicht; bitte beteiligen Sie sich an der Diskussion \u00fcber <a href=\"https:\/\/www.linkedin.com\/posts\/julian-sperling-4bba72228_connect-mggraph-mit-benutzername-und-passwort-activity-7157376467616595968-iSdN?utm_source=share&amp;utm_medium=member_desktop\">meinen Zugeh\u00f6rigen LinkedIn Post<\/a>.<\/p>\n<p><br class=\"\"><\/p>\n<p>Wenn sie an den Dingen interessiert sind die ich tue <a href=\"https:\/\/www.linkedin.com\/in\/julian-sperling-4bba72228\/\">folgen sie mir auf LinkedIn<\/a>.<\/p>\n<p><br class=\"\"><\/p>\n<h2>Volles Script<\/h2>\n<p><a href=\"https:\/\/gist.github.com\/dreadsend\/fb46410db717ca3e937acbc9fccca754\">Aktuellste Version<\/a><\/p>\n<pre><code class=\"language-powershell\">function Connect-ROPCGraph {\n    param (\n        [Parameter(ParameterSetName = &quot;PublicClient&quot;, Mandatory = $true)]\n        [Parameter(ParameterSetName = &quot;ClientCert&quot;, Mandatory = $true)]\n        [Parameter(ParameterSetName = &quot;ClientCredentials&quot;, Mandatory = $true)]\n        [ValidateNotNull()]\n        [System.Management.Automation.PSCredential]$userCredentials,\n\n        [Parameter(ParameterSetName = &quot;PublicClient&quot;, Mandatory = $true)]\n        [switch]$publicClient,\n\n        [Parameter(ParameterSetName = &quot;PublicClient&quot;, Mandatory = $false)]\n        [Parameter(ParameterSetName = &quot;ClientCert&quot;, Mandatory = $true)]\n        [Parameter(ParameterSetName = &quot;ClientCredentials&quot;, Mandatory = $true)]\n        [string]$tenantId,\n\n        [Parameter(ParameterSetName = &quot;PublicClient&quot;, Mandatory = $true)]\n        [Parameter(ParameterSetName = &quot;ClientCert&quot;, Mandatory = $true)]\n        [Parameter(ParameterSetName = &quot;ClientCredentials&quot;, Mandatory = $true)]\n        [string]$clientId,\n\n        [Parameter(ParameterSetName = &quot;PublicClient&quot;, Mandatory = $true)]\n        [Parameter(ParameterSetName = &quot;ClientCert&quot;, Mandatory = $true)]\n        [Parameter(ParameterSetName = &quot;ClientCredentials&quot;, Mandatory = $true)]\n        [ValidateNotNullOrEmpty()]\n        [array]$scopes,\n\n        [Parameter(ParameterSetName = &quot;ClientCert&quot;, Mandatory = $true)]\n        [ValidateNotNull()]\n        [string]$certificateThumbprint,\n\n        [Parameter(ParameterSetName = &quot;ClientCredentials&quot;, Mandatory = $true)]\n        [ValidateNotNull()]\n        [securestring]$clientSecret\n    )\n\n    # Depending on which Type of Client Credentials were used we generate the Request Body\n    switch ($PSCmdlet.ParameterSetName) {\n        &quot;PublicClient&quot; {\n            $Body = @{\n                client_id  = $clientId\n                scope      = [string]$scopes\n                username   = $userCredentials.UserName\n                password   = $userCredentials.GetNetworkCredential().Password\n                grant_type = &quot;password&quot;\n            }\n        }\n        &quot;ClientCert&quot; {\n            # If we are using Certificate Credentials we have to generate a JWT Assertion\n            # Based on https:\/\/adamtheautomator.com\/powershell-graph-api\/ - the original certificate usage did not work for me though\n            try {\n                # Load Certificate\n                $Certificate = Get-Item &quot;Cert:\\CurrentUser\\My\\$certificateThumbprint&quot; -ErrorAction Stop\n\n                # Get base64 hash of certificate in Web Encoding\n                $CertificateBase64Hash = [System.Convert]::ToBase64String($Certificate.GetCertHash()) -replace &#039;\\+&#039;, &#039;-&#039; -replace &#039;\/&#039;, &#039;_&#039; -replace &#039;=&#039;\n            }\n            catch {\n                throw &quot;Error Reading Certificate&quot;\n            }\n\n            $StartDate = (Get-Date &quot;1970-01-01T00:00:00Z&quot;).ToUniversalTime()\n\n            # Create JWT timestamp for expiration\n            $JWTExpirationTimeSpan = (New-TimeSpan -Start $StartDate -End (Get-Date).ToUniversalTime().AddMinutes(2)).TotalSeconds\n            $JWTExpiration = [math]::Round($JWTExpirationTimeSpan, 0)\n\n            # Create JWT validity start timestamp\n            $NotBeforeExpirationTimeSpan = (New-TimeSpan -Start $StartDate -End ((Get-Date).ToUniversalTime())).TotalSeconds\n            $NotBefore = [math]::Round($NotBeforeExpirationTimeSpan, 0)\n\n            # Create JWT header\n            $JWTHeader = @{\n                alg = &quot;RS256&quot;\n                typ = &quot;JWT&quot;\n                x5t = $CertificateBase64Hash \n            }\n\n            # Create JWT payload\n            $JWTPayLoad = @{\n                aud = &quot;https:\/\/login.microsoftonline.com\/$tenantID\/oauth2\/token&quot;\n                exp = $JWTExpiration\n                iss = $clientID\n                jti = [guid]::NewGuid()\n                nbf = $NotBefore\n                sub = $clientID\n            }\n\n            # Convert header and payload to base64\n            $JWTHeaderToByte = [System.Text.Encoding]::UTF8.GetBytes(($JWTHeader | ConvertTo-Json))\n            $EncodedHeader = [System.Convert]::ToBase64String($JWTHeaderToByte)\n\n            $JWTPayLoadToByte = [System.Text.Encoding]::UTF8.GetBytes(($JWTPayload | ConvertTo-Json))\n            $EncodedPayload = [System.Convert]::ToBase64String($JWTPayLoadToByte)\n\n            $JWT = $EncodedHeader + &quot;.&quot; + $EncodedPayload\n\n            # Define RSA signature and hashing algorithm\n            $RSAPadding = [Security.Cryptography.RSASignaturePadding]::Pkcs1\n            $HashAlgorithm = [Security.Cryptography.HashAlgorithmName]::SHA256\n\n            # Sign the JWT\n            $rsaCert = [System.Security.Cryptography.X509Certificates.RSACertificateExtensions]::GetRSAPrivateKey($Certificate)\n            $Signature = [Convert]::ToBase64String(\n                $rsaCert.SignData([System.Text.Encoding]::UTF8.GetBytes($JWT), $HashAlgorithm, $RSAPadding)\n            ) -replace &#039;\\+&#039;, &#039;-&#039; -replace &#039;\/&#039;, &#039;_&#039; -replace &#039;=&#039;\n\n            # Add Signature to JWT\n            $JWT = $JWT + &quot;.&quot; + $Signature\n\n            $Body = @{\n                client_id             = $clientId\n                client_assertion      = $JWT\n                client_assertion_type = &quot;urn:ietf:params:oauth:client-assertion-type:jwt-bearer&quot;\n                scope                 = [string]$scopes\n                username              = $userCredentials.UserName\n                password              = $userCredentials.GetNetworkCredential().Password\n                grant_type            = &quot;password&quot;\n            }\n        }\n        &quot;ClientCredentials&quot; {\n            $Body = @{\n                client_id     = $clientId\n                client_secret = [System.Net.NetworkCredential]::new(&quot;&quot;, $clientSecret).Password\n                scope         = [string]$scopes\n                username      = $userCredentials.UserName\n                password      = $userCredentials.GetNetworkCredential().Password\n                grant_type    = &quot;password&quot;\n            }\n        }\n    }\n\n    $params = @{\n        Uri         = &quot;https:\/\/login.microsoftonline.com\/$tenantID\/oauth2\/v2.0\/token&quot;\n        Method      = &#039;POST&#039;\n        ContentType = &#039;application\/x-www-form-urlencoded&#039;\n        Body        = $Body\n        # If we use a JWT we must add an Authorization Header\n        Headers     = if ($JWT) { @{ Authorization = &quot;Bearer $JWT&quot; } }\n    }\n    $accessToken = ConvertTo-SecureString (Invoke-RestMethod @params -ErrorAction Stop).access_token -AsPlainText -Force\n\n    # Use our Access token to Connect to Microsoft Graph\n    Connect-MgGraph -AccessToken $accessToken -NoWelcome\n\n    # Clear Senstive Values\n    $sensitiveVars = @(&quot;userCredentials&quot;,&quot;accessToken&quot;,&quot;body&quot;,&quot;params&quot;,&quot;jwt&quot;,&quot;signature&quot;)\n    Remove-Variable $sensitiveVars\n    [gc]::collect()\n}<\/code><\/pre>\n","protected":false},"excerpt":{"rendered":"<p>In meiner Arbeit an der praktischen Umsetzung von Kennwortrotation ohne Privileged Authentication Administrator, bin ich \u00fcber eine etwas umfangreiche Herausforderung gestolpert. Wenn man sich \u00fcber PowerShell mit Benutzername + Passwort an der Graph API anmelden will, findet man keine Kombination in der PowerShell SDK. Die einzige Methode w\u00e4re ClientID + Secret &#8211; damit w\u00e4ren wir&#8230; &raquo; <a class=\"read-more-link\" href=\"https:\/\/sparrow365.de\/index.php\/2024\/01\/28\/connect-mggraph-mit-benutzername-und-passwort\/\">weiterlesen<\/a><\/p>\n","protected":false},"author":2,"featured_media":522,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[24,48],"tags":[60,56,58,215,211,213,107,54,62,209,207],"class_list":["post-518","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-me-id","category-powershell","tag-graph","tag-graph-api","tag-microsoft-graph","tag-non-interactive","tag-oauth","tag-oidc","tag-passwoerter","tag-powershell","tag-powershell-sdk","tag-resource-owner-password-credentials","tag-ropc"],"_links":{"self":[{"href":"https:\/\/sparrow365.de\/index.php\/wp-json\/wp\/v2\/posts\/518","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/sparrow365.de\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/sparrow365.de\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/sparrow365.de\/index.php\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/sparrow365.de\/index.php\/wp-json\/wp\/v2\/comments?post=518"}],"version-history":[{"count":6,"href":"https:\/\/sparrow365.de\/index.php\/wp-json\/wp\/v2\/posts\/518\/revisions"}],"predecessor-version":[{"id":537,"href":"https:\/\/sparrow365.de\/index.php\/wp-json\/wp\/v2\/posts\/518\/revisions\/537"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/sparrow365.de\/index.php\/wp-json\/wp\/v2\/media\/522"}],"wp:attachment":[{"href":"https:\/\/sparrow365.de\/index.php\/wp-json\/wp\/v2\/media?parent=518"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/sparrow365.de\/index.php\/wp-json\/wp\/v2\/categories?post=518"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/sparrow365.de\/index.php\/wp-json\/wp\/v2\/tags?post=518"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}