From Azure Functions to APIM and Microsoft Graph: A Hands-On Architecture Journey
October 2026
Today was one of those rewarding deep-dive sessions where theory met practice. The goal was simple: understand how Azure API Management (APIM), Azure Functions, Microsoft Entra ID tokens, and Microsoft Graph can work together in a real-world enterprise authorization scenario.
The Starting Point: Building My First Java Azure Function
I started by creating a Java Azure Function locally, deploying it to Azure, and verifying that it could be invoked through a browser.
@FunctionName("HttpExample")
public HttpResponseMessage run(...)
Successfully deploying and invoking this function validated:
- Java Azure Functions runtime
- Azure deployment process
- HTTP triggers
- Azure Function hosting
- Public endpoint accessibility
Moving Back to APIM
The real objective was not the function itself. The objective was to build and understand a scalable enterprise authorization architecture.
Consumer
↓
APIM
↓
Azure Function
↓
Microsoft Graph
↓
Authorization Decision
↓
Backend API
This is a common enterprise pattern where APIM acts as a gateway and Azure Functions perform custom authorization or enrichment logic.
Calling Azure Function from APIM
I implemented an APIM policy using send-request.
<send-request mode="new"
response-variable-name="userContext">
<set-url>
https://functionapp.azurewebsites.net/api/HttpExample
</set-url>
<set-method>POST</set-method>
</send-request>
This was my first successful proof that:
APIM ↓ Azure Function ↓ Response ↓ APIM
works seamlessly.
The Function response was captured inside:
context.Variables["userContext"]
Understanding APIM send-request
One of the key learnings was that APIM can act as an HTTP client itself.
The send-request policy allows APIM to invoke:
- Azure Functions
- REST APIs
- Microsoft Graph
- Internal Services
- Validation Services
without forwarding the request to the backend.
Building a Validation Function
Next, I added a second Azure Function:
@FunctionName("ValidateApp")
The goal was:
- Receive a Bearer Token
- Decode JWT
- Extract Application ID
- Call Microsoft Graph
- Retrieve Application Name
Receiving Authorization Headers
Initially the function returned:
Authorization header missing
Investigation revealed something important.
authorization=Bearer eyJ...
instead of:
Authorization=Bearer eyJ...
This demonstrated a critical concept:
HTTP Header names are case-insensitive.
The safest implementation was:
String authHeader = request.getHeaders()
.entrySet()
.stream()
.filter(h -> h.getKey().equalsIgnoreCase("Authorization"))
.map(Map.Entry::getValue)
.findFirst()
.orElse(null);
Decoding JWT Tokens
I used:
DecodedJWT jwt = JWT.decode(token);
An important realization:
JWT.decode() does not validate the token. It simply decodes the payload and makes claims available.
Useful claims included:
appid tid aud roles
Why APIM Should Validate Tokens
In an enterprise architecture, JWT validation should not happen inside every service. Instead:
Consumer ↓ APIM ↓ validate-jwt ↓ Azure Function
APIM validates:
- Signature
- Issuer
- Audience
- Expiration
Once APIM validates the token, downstream services can trust it.
Discovering the Calling Application
A client-credentials token contained:
{
"appid":"4fa96e1a-2762-416b-b81e-bfea8b3e66de"
}
The Azure Function extracted it using:
String appId =
jwt.getClaim("appid").asString();
with fallback support for:
azp
Microsoft Graph Integration
The next step was querying Microsoft Graph.
appid ↓ Microsoft Graph ↓ Service Principal ↓ Display Name
Implementation:
TokenCredential credential =
new DefaultAzureCredentialBuilder().build();
GraphServiceClient graphClient =
new GraphServiceClient(credential);
Managed Identity Learnings
The preferred enterprise approach is:
- Enable Function App Managed Identity
- Avoid client secrets
- Use DefaultAzureCredential
- Authenticate to Graph securely
Benefits include:
- No secret management
- Automatic credential rotation
- Improved security posture
- Native Azure integration
Required Graph Permissions
To query Service Principals, the Function Managed Identity requires:
- Application.Read.All
- or Directory.Read.All
with administrator consent granted.
APIM Debugging Techniques
One useful debugging technique was returning the Authorization header directly:
<return-response>
<set-body>
@(context.Request.Headers.GetValueOrDefault("Authorization"))
</set-body>
</return-response>
This quickly proves whether APIM is receiving the token.
Liquid Templates vs APIM Expressions
Another interesting topic explored was:
template="liquid"
Liquid templates are useful for:
- JSON Transformation
- XML Transformation
- SOAP to REST conversion
- Payload reshaping
Whereas APIM expressions:
@(...)
are used for:
- Variables
- Headers
- Request Context
- Conditional Logic
The Emerging Architecture
Consumer
↓
APIM
↓
JWT Validation
↓
Azure Function
↓
Extract appid
↓
Microsoft Graph
↓
Lookup Service Principal
↓
Authorization Decision
↓
Java Backend API
This pattern centralizes authorization and keeps backend services simple.
Key Takeaways
Technical Learnings
- Built and deployed a Java Azure Function
- Invoked Functions from APIM
- Forwarded Bearer tokens through APIM
- Decoded JWT tokens
- Extracted appid claims
- Integrated with Microsoft Graph
- Explored Managed Identity authentication
- Learned Graph permission requirements
- Understood APIM policy debugging
- Learned the difference between Liquid templates and APIM expressions
Architectural Learnings
The biggest realization was:
APIM should own API governance and token validation, while Azure Functions should handle custom authorization and business-specific logic.
This creates a scalable and reusable enterprise architecture.
What's Next?
APIM
↓
validate-jwt
↓
Azure Function
↓
Microsoft Graph
↓
Retrieve Entra Groups
↓
Inject Headers
X-App-Name
X-User-Groups
↓
Backend API
At that point, APIM becomes a true enterprise gateway and Azure Functions become a reusable authorization service for the entire platform.
A productive day of learning, troubleshooting, architecture design, Azure integration, and real-world API security patterns.
No comments:
Post a Comment