Blog Post

Apps on Azure Blog
3 MIN READ

Superfast using Web App and Managed Identity to invoke Function App triggers

theringe's avatar
theringe
Icon for Microsoft rankMicrosoft
Mar 12, 2025

This method allows you to invoke Function App triggers using Managed Identity for enhanced security

TOC

  1. Introduction
  2. Setup
  3. References

 

1. Introduction

Many enterprises prefer not to use App Keys to invoke Function App triggers, as they are concerned that these fixed strings might be exposed.

This method allows you to invoke Function App triggers using Managed Identity for enhanced security.

I will provide examples in both Bash and Node.js.

 

2. Setup

1. Create a Linux Python 3.11 Function App

 

1.1. Configure Authentication to block unauthenticated callers while allowing the Web App’s Managed Identity to authenticate.

Identity Provider

Microsoft

Choose a tenant for your application and it's users

Workforce Configuration

App registration type

Create

Name

[automatically generated]

Client Secret expiration

[fit-in your business purpose]

Supported Account Type

Any Microsoft Entra Directory - Multi-Tenant

Client application requirement

Allow requests from any application

Identity requirement

Allow requests from any identity

Tenant requirement

Use default restrictions based on issuer

Token store

[checked]

 

1.2. Create an anonymous trigger. Since your app is already protected by App Registration, additional Function App-level protection is unnecessary; otherwise, you will need a Function Key to trigger it.

 

1.3. Once the Function App is configured, try accessing the endpoint directly—you should receive a 401 Unauthorized error, confirming that triggers cannot be accessed without proper Managed Identity authorization.

 

1.4. After making these changes, wait 10 minutes for the settings to take effect.

 

2. Create a Linux Node.js 20 Web App and Obtain an Access Token and Invoke the Function App Trigger Using Web App (Bash Example)

 

2.1. Enable System Assigned Managed Identity in the Web App settings.

 

2.2. Open Kudu SSH Console for the Web App.

 

2.3. Run the following commands, making the necessary modifications:

  • subscriptionsID → Replace with your Subscription ID.
  • resourceGroupsID → Replace with your Resource Group ID.
  • application_id_uri → Replace with the Application ID URI from your Function App’s App Registration.
  • https://az-9640-faapp.azurewebsites.net/api/test_trigger → Replace with the corresponding Function App trigger URL.
# Please setup the target resource to yours
subscriptionsID="01d39075-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
resourceGroupsID="XXXX"

# Variable Setting (No need to change)
identityEndpoint="$IDENTITY_ENDPOINT"
identityHeader="$IDENTITY_HEADER"
application_id_uri="api://9c0012ad-XXXX-XXXX-XXXX-XXXXXXXXXXXX"

# Install necessary tool
apt install -y jq

# Get Access Token
tokenUri="${identityEndpoint}?resource=${application_id_uri}&api-version=2019-08-01"
accessToken=$(curl -s -H "Metadata: true" -H "X-IDENTITY-HEADER: $identityHeader" "$tokenUri" | jq -r '.access_token')
echo "Access Token: $accessToken"

# Run Trigger
response=$(curl -s -o response.json -w "%{http_code}" -X GET "https://az-9640-myfa.azurewebsites.net/api/my_test_trigger" -H "Authorization: Bearer $accessToken")
echo "HTTP Status Code: $response"
echo "Response Body:"
cat response.json

 

2.4. If everything is set up correctly, you should see a successful invocation result.

 

3. Invoke the Function App Trigger Using Web App (nodejs Example)

I have also provide my example, which you can modify accordingly and save it to /home/site/wwwroot/callFunctionApp.js and run it

cd /home/site/wwwroot/
vi callFunctionApp.js
npm init -y 
npm install azure/identity axios
node callFunctionApp.js
// callFunctionApp.js
const { DefaultAzureCredential } = require("@azure/identity");
const axios = require("axios");

async function callFunctionApp() {
    try {
        const applicationIdUri = "api://9c0012ad-XXXX-XXXX-XXXX-XXXXXXXXXXXX"; // Change here

        const credential = new DefaultAzureCredential();

        console.log("Requesting token...");
        const tokenResponse = await credential.getToken(applicationIdUri);
        if (!tokenResponse || !tokenResponse.token) {
            throw new Error("Failed to acquire access token");
        }

        const accessToken = tokenResponse.token;
        console.log("Token acquired:", accessToken);

        const apiUrl = "https://az-9640-myfa.azurewebsites.net/api/my_test_trigger";  // Change here

        console.log("Calling the API now...");

        const response = await axios.get(apiUrl, {
            headers: {
                Authorization: `Bearer ${accessToken}`,
            },
        });

        console.log("HTTP Status Code:", response.status);
        console.log("Response Body:", response.data);
    } catch (error) {
        console.error("Failed to call the function", error.response ? error.response.data : error.message);
    }
}

callFunctionApp();

Below is my execution result:

 

3. References

Tutorial: Managed Identity to Invoke Azure Functions | Microsoft Learn

How to Invoke Azure Function App with Managed Identity | by Krizzia 🤖 | Medium

Configure Microsoft Entra authentication - Azure App Service | Microsoft Learn

 

 

 

 

Updated Mar 12, 2025
Version 1.0

2 Comments

  • DerekTang's avatar
    DerekTang
    Copper Contributor

    Great post! Thanks a lot ChunHan. For anyone wondering what are the $IDENTITY_ENDPOINT and $IDENTITY_HEADER, they are predefined by MS in a web app runtime, https://learn.microsoft.com/en-us/azure/app-service/overview-managed-identity?tabs=portal%2Chttp#rest-endpoint-reference.

    One thing I don't like much about this built-in setup is that it brings this extra setup of an App Registration to represent your APIs in function app and that default scope "api://9c0012ad-XXXX" that does not make much sense or even matter to a developer when I just want very basic authentication. Not to say they are useless but just in my case it's extra effort.

    If you happen to be builting an frontend, already have an App Registration for your frontend website and you don't want complicated authorization, I have below setup to validate the JWT token (ID token) acquired when user authenticate into your frontend app. for your reference. In JavaScript/TypeScript, I use higher order function. But in python, you can implement it with decorator.

    async function someDumbTask(request: HttpRequest, context: InvocationContext): Promise<HttpResponseInit> {}
    
    const handler = withIDTokenValidation(someDumbTask);
    
    app.http('aDumbTask', {
        methods: ['POST'],
        authLevel: 'anonymous',
        handler: handler
    });
    
    // ======= Implementation of withIDTokenValidation =======
    import { HttpHandler, HttpRequest, InvocationContext } from "@azure/functions";
    import jwt from 'jsonwebtoken';
    
    export function withIDTokenValidation(
        handler: HttpHandler
    ): HttpHandler {
        return async (request: HttpRequest, context: InvocationContext) => {
            try {
                const IDToken = request.headers.get('Authorization')?.replace('Bearer ', '');
    
                if (!IDToken) {
                    throw new Error("ID Token is required for validation.");
                }
    
                // Parse JWT header to get kid
                const [headerB64] = IDToken.split('.');
                const header = JSON.parse(Buffer.from(headerB64, 'base64').toString('utf-8'));
                const kid = header.kid;
                if (!kid) throw new Error('No kid found in token header');
    
                // Fetch public keys from Entra ID
                const response = await fetch(`https://login.microsoftonline.com/common/discovery/keys`, {
                    method: 'GET',
                    headers: {
                        'Content-Type': 'application/json'
                    }
                });
    
                if (!response.ok) {
                    throw new Error(`Failed to fetch keys: ${response.statusText}`);
                }
    
                const keys = await response.json();
                const key = keys.keys.find((k: any) => k.kid === kid);
                if (!key) throw new Error('No matching key found for kid');
    
                // Build public key
                const publicKey = `-----BEGIN CERTIFICATE-----\n${key.x5c[0]}\n-----END CERTIFICATE-----`;
    
                const expectedAudience = "xx-xx-xx-xx-xx"
                const expectedTenantId = "xx-xx-xx-xx-xx";
    
                // Verify token with audience check and get decoded payload
                const decoded = jwt.verify(IDToken, publicKey, { algorithms: ['RS256'], audience: expectedAudience });
    
                // Check tid (tenant ID) claim
                // decoded can be string or object, but for JWTs it's always an object
                if (typeof decoded !== 'object' || !decoded.tid) {
                    throw new Error('No tid claim found in token payload');
                }
                if (decoded.tid !== expectedTenantId) {
                    throw new Error('Token tid claim does not match expected tenant ID');
                }
    
                return handler(request, context);
            } catch (error) {
                console.error("Token validation failed:", error);
                return {
                    status: 401,
                    body: 'Unauthorized: Invalid or expired ID Token.'
                };
            }
        };
    }

     

    • theringe's avatar
      theringe
      Icon for Microsoft rankMicrosoft

      thank you for your input! it truly sparked further insights.