{"id":186,"date":"2024-12-05T20:48:17","date_gmt":"2024-12-05T19:48:17","guid":{"rendered":"https:\/\/digitalmaterial.ch\/blog\/?p=186"},"modified":"2024-12-27T20:28:48","modified_gmt":"2024-12-27T19:28:48","slug":"microsoft-graph-managed-identities","status":"publish","type":"post","link":"https:\/\/digitalmaterial.ch\/blog\/microsoft-graph-managed-identities\/","title":{"rendered":"Microsoft Graph Managed Identities"},"content":{"rendered":"\n<p>Have you ever struggled with using PowerShell modules in function apps to access resources through MS Graph? In this article, we will break down how to add a managed identity to an existing Azure Function App and grant it permissions to Graph.<\/p>\n\n\n\n<h1 class=\"wp-block-heading\">Granting permissions<\/h1>\n\n\n\n<p>Granting permissions is only possible by using the MS Graph PowerShell Module. Be sure you have installed the latest one.<\/p>\n\n\n\n<div class=\"wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers\" data-code-block-pro-font-family=\"Code-Pro-JetBrains-Mono\" style=\"font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#D4D4D4;--cbp-line-number-width:calc(1 * 0.6 * .875rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)\"><span style=\"display:flex;align-items:center;padding:10px 0px 10px 16px;margin-bottom:-2px;width:100%;text-align:left;background-color:#2b2b2b;color:#c7c7c7\">PowerShell<\/span><span role=\"button\" tabindex=\"0\" data-code=\"Install-Module -name Microsoft.Graph -scope AllUsers -Force\" style=\"color:#D4D4D4;display:none\" aria-label=\"Copy\" class=\"code-block-pro-copy-button\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" style=\"width:24px;height:24px\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" stroke-width=\"2\"><path class=\"with-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4\"><\/path><path class=\"without-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2\"><\/path><\/svg><\/span><pre class=\"shiki dark-plus\" style=\"background-color: #1E1E1E\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color: #DCDCAA\">Install-Module<\/span><span style=\"color: #D4D4D4\"> -name Microsoft.Graph -scope AllUsers -Force<\/span><\/span><\/code><\/pre><\/div>\n\n\n\n<p>While this is downloading, head over to your <strong>Function App<\/strong> and select <strong>Settings &gt; Identity &gt; System assigned<\/strong>. Make sure the status is <strong>On<\/strong>, afterwards copy the <strong>Object ID<\/strong>.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"535\" src=\"https:\/\/digitalmaterial.ch\/blog\/wp-content\/uploads\/2024\/12\/image-3-1024x535.png\" alt=\"\" class=\"wp-image-188\" srcset=\"https:\/\/digitalmaterial.ch\/blog\/wp-content\/uploads\/2024\/12\/image-3-1024x535.png 1024w, https:\/\/digitalmaterial.ch\/blog\/wp-content\/uploads\/2024\/12\/image-3-300x157.png 300w, https:\/\/digitalmaterial.ch\/blog\/wp-content\/uploads\/2024\/12\/image-3-768x401.png 768w, https:\/\/digitalmaterial.ch\/blog\/wp-content\/uploads\/2024\/12\/image-3-1536x803.png 1536w, https:\/\/digitalmaterial.ch\/blog\/wp-content\/uploads\/2024\/12\/image-3-2048x1070.png 2048w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p>Once the MS Graph module has downloaded, we can use the following script to grant our Managed Identity the needed permissions. The user connecting to MS Graph will need the following permissions <strong>Application.Read.All<\/strong> and <strong>AppRoleAssignment.ReadWrite.All <\/strong>. Be sure to add your <strong>TenantID<\/strong> and <strong>Managed Identity ObjectID<\/strong>. In our use case, the app roles <strong>&#171;Group.ReadWrite.All&#187;<\/strong>, <strong>&#171;GroupMember.ReadWrite.All&#187;<\/strong> are added.<\/p>\n\n\n\n<div class=\"wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers\" data-code-block-pro-font-family=\"Code-Pro-JetBrains-Mono\" style=\"font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#D4D4D4;--cbp-line-number-width:calc(2 * 0.6 * .875rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)\"><span style=\"display:flex;align-items:center;padding:10px 0px 10px 16px;margin-bottom:-2px;width:100%;text-align:left;background-color:#2b2b2b;color:#c7c7c7\">PowerShell<\/span><span role=\"button\" tabindex=\"0\" data-code=\"# The tenant ID\n$TenantId = &quot;xxxx-xxxx&quot;\n\n# The array of app role names that the managed identity should be assigned to.\n$appRoleNames = @(&quot;Group.ReadWrite.All&quot;, &quot;GroupMember.ReadWrite.All&quot;)\n\n#The Object principal ID we've copied from our Function App.\n$managedIdentityObjectId = &quot;xxxx-xxxx&quot;\n\nConnect-MgGraph -TenantId $TenantId -Scopes 'Application.Read.All','AppRoleAssignment.ReadWrite.All'\n\n# Get Microsoft Graph app's service principal and app roles.\n$serverApplicationName = &quot;Microsoft Graph&quot;\n$serverServicePrincipal = (Get-MgServicePrincipal -Filter &quot;DisplayName eq '$serverApplicationName'&quot;)\n$serverServicePrincipalObjectId = $serverServicePrincipal.Id\n\n# Loop through each app role name and assign the managed identity access to the app role.\nforeach ($appRoleName in $appRoleNames) {\n\u00a0 \u00a0 $appRoleId = ($serverServicePrincipal.AppRoles | Where-Object {$_.Value -eq $appRoleName }).Id\n\n\u00a0 \u00a0 # Assign the managed identity access to the app role.\n\u00a0 \u00a0 New-MgServicePrincipalAppRoleAssignment -ServicePrincipalId $managedIdentityObjectId -PrincipalId $managedIdentityObjectId -ResourceId $serverServicePrincipalObjectId -AppRoleId $appRoleId\n}\n\" style=\"color:#D4D4D4;display:none\" aria-label=\"Copy\" class=\"code-block-pro-copy-button\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" style=\"width:24px;height:24px\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" stroke-width=\"2\"><path class=\"with-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4\"><\/path><path class=\"without-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2\"><\/path><\/svg><\/span><pre class=\"shiki dark-plus\" style=\"background-color: #1E1E1E\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color: #6A9955\"># The tenant ID<\/span><\/span>\n<span class=\"line\"><span style=\"color: #9CDCFE\">$TenantId<\/span><span style=\"color: #D4D4D4\"> = <\/span><span style=\"color: #CE9178\">&quot;xxxx-xxxx&quot;<\/span><\/span>\n<span class=\"line\"><\/span>\n<span class=\"line\"><span style=\"color: #6A9955\"># The array of app role names that the managed identity should be assigned to.<\/span><\/span>\n<span class=\"line\"><span style=\"color: #9CDCFE\">$appRoleNames<\/span><span style=\"color: #D4D4D4\"> = <\/span><span style=\"color: #569CD6\">@<\/span><span style=\"color: #D4D4D4\">(<\/span><span style=\"color: #CE9178\">&quot;Group.ReadWrite.All&quot;<\/span><span style=\"color: #D4D4D4\">, <\/span><span style=\"color: #CE9178\">&quot;GroupMember.ReadWrite.All&quot;<\/span><span style=\"color: #D4D4D4\">)<\/span><\/span>\n<span class=\"line\"><\/span>\n<span class=\"line\"><span style=\"color: #6A9955\">#The Object principal ID we&#39;ve copied from our Function App.<\/span><\/span>\n<span class=\"line\"><span style=\"color: #9CDCFE\">$managedIdentityObjectId<\/span><span style=\"color: #D4D4D4\"> = <\/span><span style=\"color: #CE9178\">&quot;xxxx-xxxx&quot;<\/span><\/span>\n<span class=\"line\"><\/span>\n<span class=\"line\"><span style=\"color: #DCDCAA\">Connect-MgGraph<\/span><span style=\"color: #D4D4D4\"> -TenantId <\/span><span style=\"color: #9CDCFE\">$TenantId<\/span><span style=\"color: #D4D4D4\"> -Scopes <\/span><span style=\"color: #CE9178\">&#39;Application.Read.All&#39;<\/span><span style=\"color: #D4D4D4\">,<\/span><span style=\"color: #CE9178\">&#39;AppRoleAssignment.ReadWrite.All&#39;<\/span><\/span>\n<span class=\"line\"><\/span>\n<span class=\"line\"><span style=\"color: #6A9955\"># Get Microsoft Graph app&#39;s service principal and app roles.<\/span><\/span>\n<span class=\"line\"><span style=\"color: #9CDCFE\">$serverApplicationName<\/span><span style=\"color: #D4D4D4\"> = <\/span><span style=\"color: #CE9178\">&quot;Microsoft Graph&quot;<\/span><\/span>\n<span class=\"line\"><span style=\"color: #9CDCFE\">$serverServicePrincipal<\/span><span style=\"color: #D4D4D4\"> = (<\/span><span style=\"color: #DCDCAA\">Get-MgServicePrincipal<\/span><span style=\"color: #D4D4D4\"> -Filter <\/span><span style=\"color: #CE9178\">&quot;DisplayName eq &#39;<\/span><span style=\"color: #9CDCFE\">$serverApplicationName<\/span><span style=\"color: #CE9178\">&#39;&quot;<\/span><span style=\"color: #D4D4D4\">)<\/span><\/span>\n<span class=\"line\"><span style=\"color: #9CDCFE\">$serverServicePrincipalObjectId<\/span><span style=\"color: #D4D4D4\"> = <\/span><span style=\"color: #9CDCFE\">$serverServicePrincipal<\/span><span style=\"color: #DCDCAA\">.Id<\/span><\/span>\n<span class=\"line\"><\/span>\n<span class=\"line\"><span style=\"color: #6A9955\"># Loop through each app role name and assign the managed identity access to the app role.<\/span><\/span>\n<span class=\"line\"><span style=\"color: #C586C0\">foreach<\/span><span style=\"color: #D4D4D4\"> (<\/span><span style=\"color: #9CDCFE\">$appRoleName<\/span><span style=\"color: #D4D4D4\"> <\/span><span style=\"color: #C586C0\">in<\/span><span style=\"color: #D4D4D4\"> <\/span><span style=\"color: #9CDCFE\">$appRoleNames<\/span><span style=\"color: #D4D4D4\">) {<\/span><\/span>\n<span class=\"line\"><span style=\"color: #D4D4D4\">\u00a0 \u00a0 <\/span><span style=\"color: #9CDCFE\">$appRoleId<\/span><span style=\"color: #D4D4D4\"> = (<\/span><span style=\"color: #9CDCFE\">$serverServicePrincipal<\/span><span style=\"color: #DCDCAA\">.AppRoles<\/span><span style=\"color: #D4D4D4\"> | <\/span><span style=\"color: #DCDCAA\">Where-Object<\/span><span style=\"color: #D4D4D4\"> {<\/span><span style=\"color: #9CDCFE\">$_<\/span><span style=\"color: #DCDCAA\">.Value<\/span><span style=\"color: #D4D4D4\"> -eq <\/span><span style=\"color: #9CDCFE\">$appRoleName<\/span><span style=\"color: #D4D4D4\"> }).Id<\/span><\/span>\n<span class=\"line\"><\/span>\n<span class=\"line\"><span style=\"color: #D4D4D4\">\u00a0 \u00a0 <\/span><span style=\"color: #6A9955\"># Assign the managed identity access to the app role.<\/span><\/span>\n<span class=\"line\"><span style=\"color: #D4D4D4\">\u00a0 \u00a0 <\/span><span style=\"color: #DCDCAA\">New-MgServicePrincipalAppRoleAssignment<\/span><span style=\"color: #D4D4D4\"> -ServicePrincipalId <\/span><span style=\"color: #9CDCFE\">$managedIdentityObjectId<\/span><span style=\"color: #D4D4D4\"> -PrincipalId <\/span><span style=\"color: #9CDCFE\">$managedIdentityObjectId<\/span><span style=\"color: #D4D4D4\"> -ResourceId <\/span><span style=\"color: #9CDCFE\">$serverServicePrincipalObjectId<\/span><span style=\"color: #D4D4D4\"> -AppRoleId <\/span><span style=\"color: #9CDCFE\">$appRoleId<\/span><\/span>\n<span class=\"line\"><span style=\"color: #D4D4D4\">}<\/span><\/span>\n<span class=\"line\"><\/span><\/code><\/pre><\/div>\n\n\n\n<p>Once set, these permissions will be reflected in Entra if you head over to the Enterprise Application.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"575\" src=\"https:\/\/digitalmaterial.ch\/blog\/wp-content\/uploads\/2024\/12\/image-20240924-152150-1024x575.png\" alt=\"\" class=\"wp-image-189\" srcset=\"https:\/\/digitalmaterial.ch\/blog\/wp-content\/uploads\/2024\/12\/image-20240924-152150-1024x575.png 1024w, https:\/\/digitalmaterial.ch\/blog\/wp-content\/uploads\/2024\/12\/image-20240924-152150-300x169.png 300w, https:\/\/digitalmaterial.ch\/blog\/wp-content\/uploads\/2024\/12\/image-20240924-152150-768x431.png 768w, https:\/\/digitalmaterial.ch\/blog\/wp-content\/uploads\/2024\/12\/image-20240924-152150.png 1360w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<h1 class=\"wp-block-heading\">Connection to Microsoft Graph<\/h1>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p><strong>Disclaimer:<\/strong> To use the connection to Microsoft Graph as described, ensure that the required Microsoft Graph PowerShell modules are available in your Azure Function App. If your app does not already have these modules installed, you can refer to <a href=\"https:\/\/digitalmaterial.ch\/blog\/azure-function-app-with-large-powershell-modules\/\" target=\"_blank\" rel=\"noreferrer noopener\">this blog post<\/a> for detailed instructions on how to add them.<\/p>\n<\/blockquote>\n\n\n\n<p>Using Managed Identity to connect to Microsoft Graph is an efficient and secure way to authenticate in Azure Function Apps. This section explains how to configure your Function App to connect to Microsoft Graph at startup.<\/p>\n\n\n\n<p>The <code><strong>profile.ps1<\/strong><\/code> file in your Azure Function App is executed every time the app undergoes a &#171;cold start&#187;. This makes it an ideal place to include the necessary connection commands. Below is an example <strong><code>profile.ps1<\/code> <\/strong>that establishes the connection using the Managed Identity (MSI) of your Function App.<\/p>\n\n\n\n<p>Navigate to your Function App in the Azure Portal, then go to <strong>Functions<\/strong> and select <strong>App Files<\/strong>. From the list of files, choose <code><strong>profile.ps1<\/strong><\/code>. Once you have opened the file, add the necessary connection commands. Use the following script to establish a connection to Microsoft Graph using the Managed Identity of your Function App:<\/p>\n\n\n\n<div class=\"wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers\" data-code-block-pro-font-family=\"Code-Pro-JetBrains-Mono\" style=\"font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#D4D4D4;--cbp-line-number-width:calc(1 * 0.6 * .875rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)\"><span style=\"display:flex;align-items:center;padding:10px 0px 10px 16px;margin-bottom:-2px;width:100%;text-align:left;background-color:#2b2b2b;color:#c7c7c7\">PowerShell<\/span><span role=\"button\" tabindex=\"0\" data-code=\"# Authenticate using MSI.\nif ($env:MSI_SECRET) {\n    Connect-MgGraph -Identity\n}\" style=\"color:#D4D4D4;display:none\" aria-label=\"Copy\" class=\"code-block-pro-copy-button\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" style=\"width:24px;height:24px\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" stroke-width=\"2\"><path class=\"with-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4\"><\/path><path class=\"without-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2\"><\/path><\/svg><\/span><pre class=\"shiki dark-plus\" style=\"background-color: #1E1E1E\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color: #6A9955\"># Authenticate using MSI.<\/span><\/span>\n<span class=\"line\"><span style=\"color: #C586C0\">if<\/span><span style=\"color: #D4D4D4\"> (<\/span><span style=\"color: #9CDCFE\">$env:MSI_SECRET<\/span><span style=\"color: #D4D4D4\">) {<\/span><\/span>\n<span class=\"line\"><span style=\"color: #D4D4D4\">    <\/span><span style=\"color: #DCDCAA\">Connect-MgGraph<\/span><span style=\"color: #D4D4D4\"> -Identity<\/span><\/span>\n<span class=\"line\"><span style=\"color: #D4D4D4\">}<\/span><\/span><\/code><\/pre><\/div>\n\n\n\n<p>After adding the connection commands to the <code><strong>profile.ps1<\/strong><\/code> file, save your changes directly in the Azure Portal. To ensure the updates take effect, <strong>restart <\/strong>your Function App, which will trigger a &#171;cold start&#187; and execute the modified <code><strong>profile.ps1<\/strong><\/code> script. Finally, verify the connection to Microsoft Graph by checking the logs of your Function App to confirm that the connection was successfully established using the Managed Identity.<\/p>\n\n\n\n<h1 class=\"wp-block-heading\">Usecases<\/h1>\n\n\n\n<p>You can change the <strong>managed identity <\/strong>permissions to your liking and use any PowerShell module available to interact with Microsoft&#8217;s cloud services. Some examples for function apps are:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Cleaning up stale devices in Entra ID<\/li>\n\n\n\n<li>Automate group memberships based on user attributes like department, location, or role<\/li>\n\n\n\n<li>Onboarding and offboarding workflows<\/li>\n\n\n\n<li>Shutting down unused AVD Hosts<\/li>\n<\/ul>\n","protected":false},"excerpt":{"rendered":"<p>Have you ever struggled with using PowerShell modules in function apps to access resources through MS Graph? In this article, we will break down how to add a managed identity to an existing Azure Function App and grant it permissions to Graph. Granting permissions Granting permissions is only possible by using the MS Graph PowerShell [&hellip;]<\/p>\n","protected":false},"author":4,"featured_media":196,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[21],"tags":[],"class_list":["post-186","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-azure"],"_links":{"self":[{"href":"https:\/\/digitalmaterial.ch\/blog\/wp-json\/wp\/v2\/posts\/186","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/digitalmaterial.ch\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/digitalmaterial.ch\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/digitalmaterial.ch\/blog\/wp-json\/wp\/v2\/users\/4"}],"replies":[{"embeddable":true,"href":"https:\/\/digitalmaterial.ch\/blog\/wp-json\/wp\/v2\/comments?post=186"}],"version-history":[{"count":11,"href":"https:\/\/digitalmaterial.ch\/blog\/wp-json\/wp\/v2\/posts\/186\/revisions"}],"predecessor-version":[{"id":324,"href":"https:\/\/digitalmaterial.ch\/blog\/wp-json\/wp\/v2\/posts\/186\/revisions\/324"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/digitalmaterial.ch\/blog\/wp-json\/wp\/v2\/media\/196"}],"wp:attachment":[{"href":"https:\/\/digitalmaterial.ch\/blog\/wp-json\/wp\/v2\/media?parent=186"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/digitalmaterial.ch\/blog\/wp-json\/wp\/v2\/categories?post=186"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/digitalmaterial.ch\/blog\/wp-json\/wp\/v2\/tags?post=186"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}