{"id":4072,"date":"2023-04-13T09:53:46","date_gmt":"2023-04-13T08:53:46","guid":{"rendered":"https:\/\/www.lieben.nu\/liebensraum\/?p=4072"},"modified":"2023-04-13T09:53:46","modified_gmt":"2023-04-13T08:53:46","slug":"powershell-cert-based-authentication-against-the-graph-api-using-a-certificate-from-keyvault","status":"publish","type":"post","link":"https:\/\/lieben.nu\/liebensraum\/2023\/04\/powershell-cert-based-authentication-against-the-graph-api-using-a-certificate-from-keyvault\/","title":{"rendered":"Powershell Cert based authentication against the Graph API using a certificate from Keyvault"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">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&#8217;s work the same).<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">First, you&#8217;d need a client certificate, e.g. like this:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: powershell; title: ; notranslate\" title=\"\">\n$folder = \"c:\\users\\JosLieben\\Desktop\"\n$cert=New-SelfSignedCertificate -Subject \"CN=JOS\" -CertStoreLocation \"Cert:\\CurrentUser\\My\"  -KeyExportPolicy Exportable -KeySpec Signature -HashAlgorithm \"SHA256\" -Provider \"Microsoft Enhanced RSA and AES Cryptographic Provider\"\nExport-Certificate -Cert $cert -FilePath \"$folder\\jos.cer\" \nExport-PfxCertificate -Password (ConvertTo-SecureString $clientCertPwd -AsPlainText -Force) -Cert $cert -FilePath \"$folder\\jos.pfx\"\n<\/pre><\/div>\n\n\n<p class=\"wp-block-paragraph\">You&#8217;d then upload the .cer file as a certificate on your service principal to let Azure AD recognize your cert as a valid &#8216;password&#8217; for your app registration.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Next you&#8217;d upload your .pfx file into Keyvault.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Finally, you can use Powershell to construct an access token for a given scope:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: powershell; title: ; notranslate\" title=\"\">\n$tenantId = \"YOURTENANTID\"\n$clientId = \"YOURCLIENTID\"\n$scope = \"https:\/\/graph.microsoft.com\/.default\" #or, e.g. https:\/\/$($tenantName)-admin.sharepoint.com\/.default openid profile offline_access\n$secret = Get-AzKeyVaultSecret -VaultName \"YOURKEYVAULTNAME\" -Name cippCert -AsPlainText\n$clientCert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2 -ArgumentList @(&#x5B;Convert]::FromBase64String($secret),\"\",&#x5B;System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::Exportable)\n\n$header = @{\n    alg = \"RS256\"\n    typ = \"JWT\"\n    x5t = &#x5B;System.Convert]::ToBase64String(($clientCert.GetCertHash()))\n} | ConvertTo-Json -Compress\n\n$claimsPayload = @{\n    aud = \"https:\/\/login.microsoftonline.com\/$tenantId\/oauth2\/token\"\n    exp = &#x5B;math]::Round(((New-TimeSpan -Start ((Get-Date \"1970-01-01T00:00:00Z\" ).ToUniversalTime()) -End (Get-Date).ToUniversalTime().AddMinutes(2)).TotalSeconds),0)\n    iss = $clientId\n    jti = (New-Guid).Guid\n    nbf = &#x5B;math]::Round(((New-TimeSpan -Start ((Get-Date \"1970-01-01T00:00:00Z\" ).ToUniversalTime()) -End ((Get-Date).ToUniversalTime())).TotalSeconds),0)\n    sub = $clientId\n} | ConvertTo-Json -Compress\n\n$headerjsonbase64 = &#x5B;Convert]::ToBase64String(&#x5B;System.Text.Encoding]::UTF8.GetBytes($header)).Split('=')&#x5B;0].Replace('+', '-').Replace('\/', '_')\n$claimsPayloadjsonbase64 = &#x5B;Convert]::ToBase64String(&#x5B;System.Text.Encoding]::UTF8.GetBytes($claimsPayload)).Split('=')&#x5B;0].Replace('+', '-').Replace('\/', '_')\n\n$preJwt = $headerjsonbase64 + \".\" + $claimsPayloadjsonbase64\n$toSign = &#x5B;System.Text.Encoding]::UTF8.GetBytes($preJwt)\n$privateKey = $clientCert.PrivateKey\n$alg = &#x5B;Security.Cryptography.HashAlgorithmName]::SHA256\n$padding = &#x5B;Security.Cryptography.RSASignaturePadding]::Pkcs1\n$signature = &#x5B;Convert]::ToBase64String($privateKey.SignData($toSign,$alg,$padding)) -replace '\\+','-' -replace '\/','_' -replace '='\n\n$jwt = $headerjsonbase64 + \".\" + $claimsPayloadjsonbase64 + \".\" + $signature\n\n$Authbody = @{\n    'tenant' = $tenantId\n    'scope' = $scope\n    'client_assertion_type' = 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer'\n    'client_id'     = $clientId\n    'grant_type'    = 'client_credentials'\n    'client_assertion' = $jwt\n}        \n\n$accessToken = (Invoke-RestMethod -Method post -Uri \"https:\/\/login.microsoftonline.com\/$($tenantid)\/oauth2\/v2.0\/token\" -Body $Authbody -ErrorAction Stop).accesstoken\n\n<\/pre><\/div>\n\n\n<p class=\"wp-block-paragraph\">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:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: powershell; title: ; notranslate\" title=\"\">\n#use scope: https:\/\/$($tenantName)-admin.sharepoint.com\/.default\n\nconnect-PnPOnline -Url \"https:\/\/$($tenantName)-admin.sharepoint.com\" -AccessToken $accessToken -ReturnConnection\n<\/pre><\/div>","protected":false},"excerpt":{"rendered":"<p>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&#8217;s work the same). First, you&#8217;d need a client certificate, e.g. like this: You&#8217;d then upload the .cer file as a certificate on your service &hellip; <a href=\"https:\/\/lieben.nu\/liebensraum\/2023\/04\/powershell-cert-based-authentication-against-the-graph-api-using-a-certificate-from-keyvault\/\" class=\"more-link\">Continue reading <span class=\"screen-reader-text\">Powershell Cert based authentication against the Graph API using a certificate from Keyvault<\/span> <span class=\"meta-nav\">&rarr;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_crdt_document":"","footnotes":""},"categories":[5,7,21,39,44],"tags":[],"class_list":["post-4072","post","type-post","status-publish","format-standard","hentry","category-azure","category-azuread","category-identity","category-powershell","category-sharepoint-online"],"_links":{"self":[{"href":"https:\/\/lieben.nu\/liebensraum\/wp-json\/wp\/v2\/posts\/4072","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/lieben.nu\/liebensraum\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/lieben.nu\/liebensraum\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/lieben.nu\/liebensraum\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/lieben.nu\/liebensraum\/wp-json\/wp\/v2\/comments?post=4072"}],"version-history":[{"count":0,"href":"https:\/\/lieben.nu\/liebensraum\/wp-json\/wp\/v2\/posts\/4072\/revisions"}],"wp:attachment":[{"href":"https:\/\/lieben.nu\/liebensraum\/wp-json\/wp\/v2\/media?parent=4072"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/lieben.nu\/liebensraum\/wp-json\/wp\/v2\/categories?post=4072"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/lieben.nu\/liebensraum\/wp-json\/wp\/v2\/tags?post=4072"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}