Unlocking Seamless Authentication: Building an Azure App Service with Managed Identity Integration for Azure Functions

When one first researches how to use a managed identity to trigger an Azure function, Microsoft’s tutorial will typically be the first hit https://learn.microsoft.com/en-us/azure/spring-apps/tutorial-managed-identities-functions. While this article works perfectly for authentication, there are some important things left out that should be called out. Let’s improve this article with those missing tidbits.

Following the above tutorial, it will indeed work, but the main thing they are forgetting is that ANY managed identity in your AAD tenant can grab an access token to the function. You are essentially creating authentication and not authorization. You can easily create an app service web app and an azure function to test using a managed identity to invoke the function.

Once our Function has authentication enabled and authLevel set to anonymous in the function.json, we can test our call and see a 401 returned.

Let’s now use our managed identity from the web app to see if we can get an access token.

    So, yeah, that is from a web app that has no idea of our function, but it is from our own AAD tenant. Everyone has access! How do we quickly assign users to the application to ensure we can set specific users? The simple way is to browse to your AAD Enterprise app and ensure application assignment is enabled! This is set to No by default.

    If we call the Azure function now, it’ll return a 500.

    What happens if we cannot use option above? Well, Microsoft expects you to handle authorization inside the code. https://learn.microsoft.com/en-us/entra/identity-platform/howto-add-app-roles-in-apps explains how to create the app role. Once created, we assign an app role to the managed identity calling the function.

    $tenantID = 'yourTenantId'
    
    # The name of your web app, which has a managed identity that should be assigned to the server app's app role.
    $webAppName = 'myWebApp' #this is the webapp name that has the managed identity enabled
    $resourceGroupName = 'mywebappRg' #rg holding the webapp
    
    # The name of the function.
    $serverApplicationName = 'myFunction' # this needs to be the AAD app registration name. typically the name of the function
    
    # The name of the app role that the managed identity should be assigned to.
    $appRoleName = 'Function.Writer' # this is the custom role created in the app registration
    
    # Look up the web app's managed identity's object ID.
    $managedIdentityObjectId = (Get-AzWebApp -ResourceGroupName $resourceGroupName -Name $webAppName).identity.principalid
    
    import-module azureadpreview
    Connect-AzureAD -TenantId $tenantID 
    
    # Look up the details about the server app's service principal and app role.
    $serverServicePrincipal = (Get-AzureADServicePrincipal -Filter "DisplayName eq '$serverApplicationName'") #if you have managed identity enabled on the function,
    $serverServicePrincipalObjectId = $serverServicePrincipal.objectid #change this to the object id if you have managed identity enabled
    $appRoleId = ($serverServicePrincipal.AppRoles | Where-Object {$_.Value -eq $appRoleName }).Id
    
    # Assign the managed identity access to the app role. #this will show the webapp spn in users and groups
    New-AzureADServiceAppRoleAssignment `
        -ObjectId $managedIdentityObjectId `
        -Id $appRoleId `
        -PrincipalId $managedIdentityObjectId `
        -ResourceId $serverServicePrincipalObjectId
    

    Now that we have our managed identity assigned to the specific role, we can check the claims from the headers to handle our own authorization.

    
    $xMsClientPrincipal = $Request.Headers['X-MS-CLIENT-PRINCIPAL']
    $decodedHeaderBytes = [System.Convert]::FromBase64String($xMsClientPrincipal)
    $decodedHeader = [System.Text.Encoding]::UTF8.GetString($decodedHeaderBytes)
    $userPrincipal = $decodedHeader | ConvertFrom-Json
    
    $roles = $userPrincipal.claims | where-object { $_.typ -eq 'roles' }
    
    if ($roles.val -eq 'Function.Writer') {
        write-host "user is authorized"
    ...

    Going back to our webapp, let’s invoke the function with our managed identity. Now we can check the claims to see if we have specific roles or if the user is just not authorized.

    Hope this helps with the authorization portion of your managed identity being used to call an Azure function. Cheers!