- Visualizing your architecture with a sequence diagram and class diagram so we’re clear on the moving parts.
- Targeted OAuth troubleshooting for your Azure Function App → MCP → Azure AI Search setup over private endpoints.
1. Sequence Diagram
Here’s a Mermaid sequence diagram showing the flow:
Mermaid
sequenceDiagram
participant User as Website (Browser)
participant MCP as MCP Service (Private Endpoint)
participant Func as Azure Function App
participant OAuth as Azure Entra ID (OAuth)
participant Search as Azure AI Search (Private Endpoint)
participant Blob as Azure Blob Storage (Private Endpoint)
User->>MCP: HTTPS Request (Authenticated via OAuth)
MCP->>OAuth: OAuth Token Request (Client Credentials / Auth Code)
OAuth-->>MCP: Access Token (Bearer)
MCP->>Func: Forward Request with Token
Func->>Search: REST API Call (Index Query/Run) with Token
Search->>Blob: Read Files from Blob Storage
Blob-->>Search: File Content
Search-->>Func: Search Results / Indexing Status
Func-->>MCP: Response
MCP-->>User: Final Response
2. Class Diagram
MermaidCopy codeclassDiagram
class Website {
+sendRequest()
+displayResults()
}
class MCPService {
+authenticateWithOAuth()
+forwardRequest()
+enforcePrivateEndpoint()
}
class AzureFunctionApp {
+triggerIndexer()
+callSearchAPI()
+handleOAuthToken()
}
class AzureAIsearch {
+runIndexer()
+queryIndex()
+connectToBlob()
}
class BlobStorage {
+listFiles()
+readFile()
}
class AzureEntraID {
+issueToken()
+validateToken()
}
Website --> MCPService
MCPService --> AzureFunctionApp
AzureFunctionApp --> AzureAIsearch
AzureAIsearch --> BlobStorage
MCPService --> AzureEntraID
AzureFunctionApp --> AzureEntraID
walk you through every single step to set up OAuth correctly for your Azure Function App → MCP → Azure AI Search (private endpoints) → Blob Storage architecture.
This will be end-to-end, so you can follow it without missing anything.
We’ll cover App Registration, Permissions, Network, Function App config, and Testing.
Step-by-Step OAuth Setup
1. Plan Your Authentication Flow
You have two main options:
- Managed Identity (recommended for Function Apps in Azure) — no secrets, simpler, more secure.
- Client Credentials (OAuth 2.0) — requires Client ID + Secret.
If you can, use Managed Identity — it avoids most OAuth pitfalls.
If you must use Client Credentials, follow all steps below.
2. Create / Configure Azure Entra ID App Registration
- Go to Azure Portal → Azure Entra ID → App registrations → New registration.
- Name:
FunctionApp-MCP-Search-Blob - Supported account types:
- Usually: Accounts in this organizational directory only.
- Redirect URI:
- For
client_credentialsflow, you can skip this. - For
authorization_codeflow (browser-based), set it to your MCP or Function App callback URL exactly.
- For
- Click Register.
3. Add API Permissions
- In the App Registration → API permissions → Add a permission:
- Microsoft APIs → Azure Cognitive Search →
user_impersonation(or use.defaultscope later). - Microsoft APIs → Azure Storage →
user_impersonation.
- Microsoft APIs → Azure Cognitive Search →
- Click Add permissions.
- Click Grant admin consent for your tenant.
4. Create Client Secret (if not using Managed Identity)
- In App Registration → Certificates & secrets → New client secret.
- Description:
FunctionAppSecret - Expires: Choose 6–12 months (rotate before expiry).
- Copy the Value immediately — you won’t see it again.
5. Assign Roles to the Identity
Whether using Managed Identity or App Registration:
- Azure AI Search: Assign Search Index Data Contributor role.
- Blob Storage: Assign Storage Blob Data Reader role.
- Scope: Assign at the resource level (Search service and Storage account).
6. Configure Network for OAuth
OAuth endpoints are public — even if Search and Blob are private.
- Ensure outbound HTTPS (port 443) from Function App to:
login.microsoftonline.comlogin.microsoft.comsts.windows.net
- If using Private DNS Zones:
- Do not override
microsoftonline.com. - Ensure
privatelink.search.windows.netandprivatelink.blob.core.windows.netresolve to private IPs.
- Do not override
7. Configure Azure Function App Settings
In Function App → Configuration → Application settings:
- If using Client Credentials:Copy code
TENANT_ID=<your-tenant-id> CLIENT_ID=<your-app-registration-client-id> CLIENT_SECRET=<your-client-secret> SEARCH_SERVICE=<your-search-service-name> - If using Managed Identity:Copy code
SEARCH_SERVICE=<your-search-service-name> - Ensure VNet integration is enabled if you need private endpoint access.
8. Implement OAuth in Code
Client Credentials Example:
PythonCopy codefrom azure.identity import ClientSecretCredential
import requests, os
tenant_id = os.environ["TENANT_ID"]
client_id = os.environ["CLIENT_ID"]
client_secret = os.environ["CLIENT_SECRET"]
search_service = os.environ["SEARCH_SERVICE"]
cred = ClientSecretCredential(tenant_id, client_id, client_secret)
token = cred.get_token("https://search.azure.com/.default")
url = f"https://{search_service}.search.windows.net/indexes?api-version=2023-11-01"
headers = {"Authorization": f"Bearer {token.token}"}
r = requests.get(url, headers=headers)
print(r.status_code, r.text)
Managed Identity Example:
PythonCopy codefrom azure.identity import ManagedIdentityCredential
import requests, os
search_service = os.environ["SEARCH_SERVICE"]
cred = ManagedIdentityCredential()
token = cred.get_token("https://search.azure.com/.default")
url = f"https://{search_service}.search.windows.net/indexes?api-version=2023-11-01"
headers = {"Authorization": f"Bearer {token.token}"}
r = requests.get
proceed and finish the OAuth setup guide with the final implementation, validation, and troubleshooting steps so you can be 100% confident it’s working in your
Azure Function App → MCP → Azure AI Search (private endpoints) → Blob Storage flow.
9. Validate OAuth Token
Before wiring it into MCP or your production code, test the token manually.
From Function App Kudu Console or Cloud Shell:
BashCopy codecurl -X POST \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "client_id=$CLIENT_ID" \
-d "client_secret=$CLIENT_SECRET" \
-d "scope=https://search.azure.com/.default" \
-d "grant_type=client_credentials" \
https://login.microsoftonline.com/$TENANT_ID/oauth2/v2.0/token
✅ Expected: JSON with access_token and expires_in.
❌ If fails:
- Check
TENANT_ID,CLIENT_ID,CLIENT_SECRETvalues. - Ensure outbound HTTPS to
login.microsoftonline.comis allowed. - Verify API permissions and Admin Consent in Azure Entra ID.
10. Test Azure AI Search API with Token
Once you have the token:
BashCopy codecurl -X GET \
-H "Authorization: Bearer <access_token>" \
"https://<search-service>.search.windows.net/indexes?api-version=2023-11-01"
✅ Expected: JSON listing indexes.
❌ If fails with 403/401:
- Token audience mismatch — check that you requested
https://search.azure.com/.default. - Missing Search Index Data Contributor role for the identity.
11. Test Blob Storage Access (If Indexer Reads from Blob)
If your Search indexer pulls from Blob Storage:
BashCopy codecurl -X GET \
-H "Authorization: Bearer <access_token>" \
"https://<storage-account>.blob.core.windows.net/<container>?restype=container&comp=list"
✅ Expected: XML listing blobs.
❌ If fails:
- Missing Storage Blob Data Reader role.
- Private endpoint DNS misconfiguration for Blob.
12. Integrate with MCP
Once OAuth works in isolation:
- MCP should forward the token from the Function App or request its own token using the same method.
- If MCP is in a private subnet, ensure it also has outbound HTTPS to
login.microsoftonline.com.
13. Common Pitfalls Checklist
| Issue | Symptom | Fix |
|---|---|---|
| Private DNS hijacking | DNS to login.microsoftonline.com resolves to private IP | Remove private zone for microsoftonline.com |
| Outbound HTTPS blocked | Curl to OAuth endpoint fails | Update NSG/firewall to allow port 443 |
| Wrong scope | Token works but API returns 401 | Use https://search.azure.com/.default |
| Missing admin consent | Token request fails with AADSTS65001 | Grant admin consent in Entra ID |
| Role missing | Token works but API returns 403 | Assign Search Index Data Contributor / Blob Data Reader |
| Expired secret | Token request fails | Rotate client secret |
14. Final Verification Flow
Run this inside your Function App to confirm all hops:
PythonCopy codefrom azure.identity import ClientSecretCredential, ManagedIdentityCredential
import os, requests
search_service = os.environ["SEARCH_SERVICE"]
scope = "https://search.azure.com/.default"
if os.environ.get("CLIENT_SECRET"):
cred = ClientSecretCredential(
os.environ["TENANT_ID"],
os.environ["CLIENT_ID"],
os.environ["CLIENT_SECRET"]
)
else:
cred = ManagedIdentityCredential()
token = cred.get_token(scope)
print("✅ Token acquired")
url = f"https://{search_service}.search.windows.net/indexes?api-version=2023-11-01"
headers = {"Authorization": f"Bearer {token.token}"}
r = requests.get(url, headers=headers)
print(f"Search API status: {r.status_code}")
print(r.text[:200] + "...")
✅ If this works — OAuth is fully configured and your Function App can securely talk to Azure AI Search over private endpoints.
the OAuth handshake diagram for your
Azure Function App → MCP → Azure AI Search (private endpoints) → Blob Storage setup.
This will clearly show your private endpoint traffic vs public OAuth traffic, so your network/security team can validate the flow.
Mermaid OAuth Handshake Diagram
MermaidCopy codesequenceDiagram
participant User as Website (Browser)
participant MCP as MCP Service (Private Endpoint)
participant Func as Azure Function App (VNet Integrated)
participant OAuth as Azure Entra ID (Public Endpoint)
participant Search as Azure AI Search (Private Endpoint)
participant Blob as Azure Blob Storage (Private Endpoint)
User->>MCP: HTTPS Request (User Action)
MCP->>Func: Forward API Request
Note over Func,OAuth: Step 1: Function App requests OAuth token
via Client Credentials or Managed Identity
Func->>OAuth: POST /{tenant}/oauth2/v2.0/token (Public Internet)
OAuth-->>Func: Access Token (Bearer)
Note over Func,Search: Step 2: Function App calls Azure AI Search
over Private Endpoint with token
Func->>Search: GET /indexes?api-version=2023-11-01 (Bearer token)
Search->>Blob: Read files from Blob Storage (Private Endpoint)
Blob-->>Search: File Content
Search-->>Func: Search Results / Indexing Status
Func-->>MCP: Response
MCP-->>User: Final Response
Key Points for Security Review
- OAuth endpoints (
login.microsoftonline.com) remain public — must allow outbound HTTPS from Function App and MCP. - Azure AI Search & Blob Storage traffic stays private — resolved via Private DNS Zones to private IPs.
- Token never leaves your Azure environment — MCP and Function App handle it internally.
- Role-based access control ensures token is scoped only to required services:
Search Index Data Contributorfor SearchStorage Blob Data Readerfor Blob
- No secrets in code if using Managed Identity — reduces risk.
Traffic Flow Summary
- Public Path: Function App → Azure Entra ID (OAuth token request)
- Private Path: Function App → Azure AI Search → Blob Storage
- Token Scope:
https://search.azure.com/.default(and optionallyhttps://storage.azure.com/.default)
. OAuth Connectivity Failure Troubleshooting (Private Endpoints)
When OAuth fails in a private endpoint setup, the most common issues are DNS resolution, firewall rules, and redirect URI mismatches.
Checklist
A. Network & DNS
- Ensure the Function App’s VNet integration allows outbound traffic to
login.microsoftonline.comandlogin.microsoft.com(OAuth endpoints are public, even if your services are private). - If you’re using Private DNS Zones, make sure you haven’t overridden public Microsoft identity endpoints.
- Test from the Function App console:BashCopy code
curl -v https://login.microsoftonline.com/<tenant-id>/v2.0/.well-known/openid-configuration
B. Redirect URI
- In Azure Entra ID App Registration, confirm:
- Redirect URI matches exactly what MCP or Function App uses (case-sensitive, no trailing slash mismatch).
- If using
authorization_codeflow, the URI must be reachable from the browser. - For
client_credentialsflow, redirect URI is not used — but scopes must be correct.
C. Scopes & Permissions
- For Azure AI Search:
https://search.azure.com/.default - For Blob Storage:
https://storage.azure.com/.default - Grant Admin Consent in Azure Entra ID.
D. Private Endpoint Gotchas
- Azure AI Search and Blob Storage can be private, but OAuth endpoints cannot — they must remain public.
- If your Function App is in a locked-down VNet, you must allow outbound to Microsoft identity IP ranges.
E. Token Validation
- From the Function App, try:PythonCopy code
import requests, os token_url = f"https://login.microsoftonline.com/{os.environ['TENANT_ID']}/oauth2/v2.0/token" data = { "client_id": os.environ["CLIENT_ID"], "client_secret": os.environ["CLIENT_SECRET"], "scope": "https://search.azure.com/.default", "grant_type": "client_credentials" } r = requests.post(token_url, data=data) print(r.status_code, r.text)- If this fails, it’s OAuth config/network.
- If it works, the issue is MCP → Function App token forwarding.
✅ Key Insight:
In private endpoint setups, **OAuth always requires public identity endpoints
–
if your direct token request from the Azure Function App still failed, that means we’re not just dealing with an MCP forwarding issue — the problem is likely in network reachability, identity configuration, or TLS handshake.
Let’s go deeper and systematically isolate where the OAuth flow is breaking.
Step 1 — Confirm Network Path to Azure Entra ID
Even with private endpoints for Blob and Search, OAuth endpoints are always public.
If your Function App is VNet-integrated, outbound traffic to login.microsoftonline.com might be blocked.
Test from Function App Console (Kudu / SSH):
BashCopy codenslookup login.microsoftonline.com
curl -v https://login.microsoftonline.com/common/discovery/v2.0/keys
- If DNS fails → Check Private DNS Zones; remove or override any private zone for
microsoftonline.com. - If curl fails → Check NSG, firewall, or route table blocking outbound HTTPS.
Step 2 — Check TLS & Cipher Compatibility
Some locked-down VNets or custom outbound proxies strip TLS handshakes.
Test TLS handshake:
BashCopy codeopenssl s_client -connect login.microsoftonline.com:443 -servername login.microsoftonline.com
- If handshake fails, your outbound path is blocked or intercepting SSL.
Step 3 — Validate App Registration
In Azure Entra ID → App Registrations:
- Client ID and Client Secret match exactly.
- Allowed grant type matches your flow (
client_credentialsfor Function App → MCP → Search). - API permissions:
https://search.azure.com/.defaulthttps://storage.azure.com/.default(if Blob access is needed)
- Admin consent granted.
Step 4 — Try Token Request from Outside the VNet
Run the same Python requests.post() from:
- Your local machine
- Azure Cloud Shell
If it works outside but fails inside → network isolation issue in your Function App’s VNet.
Step 5 — Use Managed Identity Instead of Client Secret
If possible, skip OAuth client credentials entirely:
- Enable System-Assigned Managed Identity on the Function App.
- Assign:
- Search Index Data Contributor on Azure AI Search
- Storage Blob Data Reader on Blob Storage
- In code:PythonCopy code
from azure.identity import ManagedIdentityCredential cred = ManagedIdentityCredential() token = cred.get_token("https://search.azure.com/.default") print(token.token)- If this works, you’ve bypassed the need for outbound OAuth calls to public endpoints with secrets.
Step 6 — Check MCP’s Role in the Flow
If MCP is still part of the authentication chain:
- Ensure MCP itself can reach
login.microsoftonline.com. - If MCP is in a private subnet, it may need a NAT Gateway or outbound rule.
💡 Pro Tip:
In 100% private endpoint architectures, the most common OAuth failure is DNS hijacking — a Private DNS Zone accidentally intercepts login.microsoftonline.com and points it to a non-public IP, breaking the token request.
let’s map out the failure-path sequence diagram so you can see exactly where OAuth can break in your Azure Function App → MCP → Azure AI Search (private endpoints) setup.
This will help you test each hop in isolation and pinpoint the failure.
Failure Path Sequence Diagram
MermaidCopy codesequenceDiagram
participant User as Website (Browser)
participant MCP as MCP Service (Private Endpoint)
participant Func as Azure Function App (VNet Integrated)
participant OAuth as Azure Entra ID (Public Endpoint)
participant Search as Azure AI Search (Private Endpoint)
participant Blob as Azure Blob Storage (Private Endpoint)
User->>MCP: HTTPS Request
MCP->>Func: Forward Request
Func->>OAuth: Request Access Token (client_credentials)
Note over Func,OAuth: ❌ Possible Failures:
1. DNS resolution blocked
2. Outbound HTTPS blocked
3. TLS handshake failure
4. Invalid client credentials
5. Wrong scope or tenant ID
OAuth-->>Func: Access Token (Bearer)
Func->>Search: REST API Call with Token
Note over Func,Search: ❌ Possible Failures:
6. Token audience mismatch
7. Role assignment missing
8. Private endpoint DNS misconfig
Search->>Blob: Read Files
Note over Search,Blob: ❌ Possible Failures:
9. Blob role missing
10. Private endpoint DNS misconfig
Blob-->>Search: File Content
Search-->>Func: Search Results
Func-->>MCP: Response
MCP-->>User: Final Response
How to Test Each Hop
Hop 1: Function App → Azure Entra ID
- Goal: Ensure token request works from inside the VNet.
- Test:BashCopy code
curl -v https://login.microsoftonline.com/<tenant-id>/v2.0/.well-known/openid-configuration- If fails → DNS or outbound HTTPS issue.
- If succeeds → Try Python token request with
client_credentials.
Hop 2: Token Audience Validation
- Goal: Ensure token is valid for Azure AI Search.
- Test:PythonCopy code
from azure.identity import ClientSecretCredential cred = ClientSecretCredential("<tenant-id>", "<client-id>", "<client-secret>") token = cred.get_token("https://search.azure.com/.default") print(token.token)- If fails → Wrong scope, missing admin consent, or invalid secret.
Hop 3: Function App → Azure AI Search (Private Endpoint)
- Goal: Ensure Function App can reach Search endpoint.
- Test:BashCopy code
nslookup <search-service-name>.search.windows.net curl -v https://<search-service-name>.search.windows.net/indexes?api-version=2023-11-01 -H "Authorization: Bearer <token>"- If DNS resolves to private IP but fails to connect → NSG or firewall issue.
Hop 4: Azure AI Search → Blob Storage (Private Endpoint)
- Goal: Ensure Search indexer can read from Blob.
- Test: In Azure Portal, run the indexer manually and check logs.
- If fails → Role assignment or private DNS misconfig for Blob.
Common OAuth Failure Root Causes in This Setup
- Private DNS Zone hijacking
login.microsoftonline.com→ breaks token requests. - Outbound HTTPS blocked from Function App to public Microsoft identity endpoints.
- Wrong OAuth scope (
https://search.azure.com/.defaultvshttps://storage.azure.com/.default). - Missing admin consent for API permissions.
- Token audience mismatch — token issued for wrong resource.
- Expired or incorrect client secret in Function App configuration.
If you want, I can now give you a step-by-step “isolation test script” that you can run inside your Function App to automatically check all 4 hops and print exactly where it fails.
That way, you’ll know in one run whether it’s network, OAuth config, or private endpoint DNS.
let’s build you a self-contained diagnostic script that you can run inside your Azure Function App (via Kudu console or as a temporary function) to test all 4 hops in your
Azure Function App → MCP → Azure AI Search (private endpoints) → Blob Storage OAuth flow.
This will pinpoint exactly where the failure is happening.
Python Diagnostic Script
Pythonimport os
import socket
import requests
from azure.identity import ClientSecretCredential, ManagedIdentityCredential
TENANT_ID = os.environ.get("TENANT_ID")
CLIENT_ID = os.environ.get("CLIENT_ID")
CLIENT_SECRET = os.environ.get("CLIENT_SECRET")
SEARCH_SERVICE = os.environ.get("SEARCH_SERVICE") # without .search.windows.net
BLOB_ACCOUNT = os.environ.get("BLOB_ACCOUNT") # without .blob.core.windows.net
def test_dns(host):
print(f"\n[DNS] Resolving {host}...")
try:
ip = socket.gethostbyname(host)
print(f"✅ DNS resolved: {ip}")
return True
except Exception as e:
print(f"❌ DNS resolution failed: {e}")
return False
def test_https(url):
print(f"\n[HTTPS] Connecting to {url}...")
try:
r = requests.get(url, timeout=10)
print(f"✅ HTTPS OK: {r.status_code}")
return True
except Exception as e:
print(f"❌ HTTPS connection failed: {e}")
return False
def test_oauth(scope):
print(f"\n[OAuth] Requesting token for scope: {scope}")
try:
if CLIENT_SECRET:
cred = ClientSecretCredential(TENANT_ID, CLIENT_ID, CLIENT_SECRET)
else:
cred = ManagedIdentityCredential()
token = cred.get_token(scope)
print(f"✅ Token acquired, length={len(token.token)}")
return token.token
except Exception as e:
print(f"❌ OAuth token request failed: {e}")
return None
def test_search_api(token):
url = f"https://{SEARCH_SERVICE}.search.windows.net/indexes?api-version=2023-11-01"
print(f"\n[Search API] Calling {url}...")
try:
r = requests.get(url, headers={"Authorization": f"Bearer {token}"}, timeout=10)
print(f"✅ Search API response: {r.status_code}")
print(r.text[:200] + "...")
return True
except Exception as e:
print(f"❌ Search API call failed: {e}")
return False
if __name__ == "__main__":
# Hop 1: DNS & HTTPS to Azure Entra ID
test_dns("login.microsoftonline.com")
test_https(f"https://login.microsoftonline.com/{TENANT_ID}/v2.0/.well-known/openid-configuration")
# Hop 2: OAuth Token Request
token = test_oauth("https://search.azure.com/.default")
if not token:
print("\n🚨 STOP: OAuth failed — check client ID/secret, scopes, admin consent, or network access.")
exit(1)
# Hop 3: DNS & HTTPS to Azure AI Search Private Endpoint
test_dns(f"{SEARCH_SERVICE}.search.windows.net")
test_https(f"https://{SEARCH_SERVICE}.search.windows.net")
# Hop 4: Call Azure AI Search API
test_search_api(token)
How to Use
- Save this as
diagnostic.pyin your Function App. - Set environment variables in Azure Function App → Configuration:
TENANT_IDCLIENT_ID/CLIENT_SECRET(or leave secret empty to test Managed Identity)SEARCH_SERVICE(e.g.,mysearchservice)BLOB_ACCOUNT(optional for later Blob tests)
- Run it from Kudu console:BashCopy code
python diagnostic.py - The script will:
- Test DNS resolution for OAuth and Search endpoints.
- Test HTTPS connectivity.
- Attempt OAuth token acquisition.
- Call Azure AI Search API with the token.
How to Read Results
- Fails at DNS step → Private DNS zone misconfiguration.
- Fails at HTTPS step → NSG/firewall blocking outbound.
- Fails at OAuth token request → Wrong credentials, missing admin consent, or blocked public endpoint.
- Fails at Search API call → Token audience mismatch, missing role assignment, or private endpoint DNS issue.
let’s proceed and complete this with a full troubleshooting playbook so you can run the diagnostic script, interpret the results, and fix the root cause quickly.
1. Run the Diagnostic Script
Use the Python script I gave you in the Function App’s Kudu console or as a temporary function.
It will test:
- DNS resolution to
login.microsoftonline.comand your Search private endpoint. - HTTPS connectivity to those endpoints.
- OAuth token acquisition (client credentials or managed identity).
- Azure AI Search API call with the token.
2. Troubleshooting by Failure Point
A. DNS Resolution Fails
Symptom:
Copy code❌ DNS resolution failed: [Errno ...]
Likely Causes:
- Private DNS Zone for
microsoftonline.comorsearch.windows.netincorrectly pointing to internal IP. - Missing Private DNS Zone link to your VNet for Search or Blob.
Fix:
- Remove any private DNS zone for
microsoftonline.com(OAuth endpoints must stay public). - Ensure you have a Private DNS Zone for
privatelink.search.windows.netlinked to your VNet for Search private endpoint. - Run:BashCopy code
nslookup login.microsoftonline.com nslookup <search-service>.search.windows.net
B. HTTPS Connectivity Fails
Symptom:
Copy code❌ HTTPS connection failed: ...
Likely Causes:
- NSG or firewall blocking outbound port 443.
- No NAT Gateway or outbound route for VNet-integrated Function App.
- TLS interception or SSL handshake failure.
Fix:
- Allow outbound HTTPS to:
login.microsoftonline.com(OAuth)<search-service>.search.windows.net(Search)
- If using private endpoints, ensure outbound to public Microsoft identity endpoints is still allowed.
- Test from Kudu:BashCopy code
curl -v https://login.microsoftonline.com
C. OAuth Token Request Fails
Symptom:
Copy code❌ OAuth token request failed: ...
Likely Causes:
- Wrong
TENANT_ID,CLIENT_ID, orCLIENT_SECRET. - Missing admin consent for required scopes.
- Wrong scope (
https://search.azure.com/.defaultfor Search,https://storage.azure.com/.defaultfor Blob). - Public endpoint blocked by network rules.
Fix:
- In Azure Entra ID → App Registrations:
- Verify credentials.
- Add API permissions for Azure AI Search and Blob Storage.
- Click Grant admin consent.
- If possible, switch to Managed Identity to avoid secrets:PythonCopy code
from azure.identity import ManagedIdentityCredential cred = ManagedIdentityCredential() token = cred.get_token("https://search.azure.com/.default")
D. Search API Call Fails
Symptom:
Copy code❌ Search API call failed: 403 / 401
Likely Causes:
- Token audience mismatch (token issued for wrong resource).
- Missing Search Index Data Contributor role for the identity.
- Private endpoint DNS misconfiguration.
Fix:
- Assign the Function App’s identity the Search Index Data Contributor role on the Search service.
- Ensure DNS for
<search-service>.search.windows.netresolves to the private IP. - Test:BashCopy code
nslookup <search-service>.search.windows.net
E. Blob Access Fails (Indexer Stage)
Symptom: Indexer logs show “cannot access blob”. Likely Causes:
- Missing Storage Blob Data Reader role for the identity.
- Private endpoint DNS misconfiguration for Blob.
Fix:
- Assign Storage Blob Data Reader role to the identity.
- Ensure DNS for
<account>.blob.core.windows.netresolves to the private IP.
3. Recommended Fix Order
- Fix DNS — make sure public endpoints stay public, private endpoints resolve correctly.
- Fix outbound HTTPS — allow Function App to reach OAuth endpoints.
- Fix OAuth config — correct credentials, scopes, and admin consent.
- Fix role assignments — Search Index Data Contributor + Blob Data Reader.
- Retest with script — confirm all hops pass.
✅ Key Insight:
In private endpoint architectures, OAuth is the #1 failure point because people accidentally try to route login.microsoftonline.com through private DNS, which breaks the public identity flow.
you want to call an Azure Function but require the Function Key for authentication, and have that call go through an API Gateway (like Azure API Management or MCP acting as a gateway).
This is a common pattern when you want to:
- Keep the Function App private (VNet + private endpoint).
- Require a function key for access.
- Route all calls through a gateway for security, logging, and throttling.
1. How Azure Function Keys Work
Azure Functions supports three key types:
- Host Keys — apply to all functions in the app.
- Function Keys — apply to a specific function.
- System Keys — used for system-level triggers.
When you require a function key, the caller must include it in the request:
Copy codeGET https://<function-app>.azurewebsites.net/api/<function-name>?code=<function-key>
or in the header:
Copy codex-functions-key: <function-key>
2. Architecture with Gateway
Here’s the flow when using Azure API Management (APIM) or MCP as a gateway:
MermaidCopy codesequenceDiagram
participant Client as Client App / Website
participant Gateway as API Gateway (APIM / MCP)
participant Func as Azure Function App (Private Endpoint)
Client->>Gateway: HTTPS Request (No Function Key)
Gateway->>Func: HTTPS Request with Function Key (Private Endpoint)
Func-->>Gateway: Response
Gateway-->>Client: Response
3. Step-by-Step Setup
Step 1 — Enable Function Key Requirement
- In Azure Portal → Function App → Your Function → Function Keys.
- Create a new key (e.g.,
gatewayKey). - Copy the key value.
Step 2 — Restrict Direct Access
- If using private endpoints, the Function App is already inaccessible from the public internet.
- If public, use Access Restrictions to allow only the gateway’s IP.
Step 3 — Configure Gateway to Inject Function Key
If using Azure API Management:
- In APIM → Create an API that points to your Function App’s private endpoint.
- In the Inbound Processing Policy, add:
XmlCopy code<set-header name="x-functions-key" exists-action="override">
<value>YOUR_FUNCTION_KEY</value>
</set-header>
or append it to the query string:
XmlCopy code<set-query-parameter name="code" exists-action="override">
<value>YOUR_FUNCTION_KEY</value>
</set-query-parameter>
If using MCP as a gateway:
- MCP’s outbound call configuration should include the
x-functions-keyheader with the stored key.
Step 4 — Secure the Gateway
- Require OAuth/JWT or API key at the gateway level for clients.
- The Function Key is never exposed to the client — only the gateway knows it.
Step 5 — Test the Flow
- Call the gateway endpoint without the function key — should succeed because the gateway injects it.
- Call the Function App directly without the key — should fail with
401 Unauthorized.
4. Benefits of This Pattern
- Key secrecy — clients never see the Function Key.
- Centralized security — gateway handles authentication, rate limiting, logging.
- Private networking — Function App can be private, only reachable from gateway.
5. Example APIM Policy
XmlCopy code<policies>
<inbound>
<base />
<set-header name="x-functions-key" exists-action="override">
<value>{{function-key-secret}}</value>
</set-header>
</inbound>
<backend>
<base />
</backend>
<outbound>
<base />
</outbound>
</policies>
Here, {{function-key-secret}} is stored in APIM Named Values (secure).
proceed and combine the OAuth client authentication with Function Key injection through the gateway into a single visual so your security/network team can see exactly how the layers work together.
OAuth + Function Key Gateway Pattern
MermaidCopy codesequenceDiagram
participant Client as Client App / Website
participant Gateway as API Gateway (APIM / MCP)
participant OAuth as Azure Entra ID (Public Endpoint)
participant Func as Azure Function App (Private Endpoint)
%% Step 1: Client Authenticates
Client->>OAuth: Request OAuth Token (client_credentials / auth_code)
OAuth-->>Client: Access Token (JWT)
%% Step 2: Client Calls Gateway
Client->>Gateway: HTTPS Request + Bearer Token
Gateway->>Gateway: Validate JWT (OAuth Scope, Expiry, Signature)
%% Step 3: Gateway Calls Function App
Note over Gateway,Func: Gateway injects Function Key
into header or query string
Gateway->>Func: HTTPS Request + x-functions-key (Private Endpoint)
Func-->>Gateway: Response
%% Step 4: Gateway Returns to Client
Gateway-->>Client: Response
How This Works
- Client Authentication (OAuth)
- The client never knows the Function Key.
- The client authenticates with Azure Entra ID and gets a JWT access token.
- The token is validated at the gateway.
- Gateway Authorization
- Gateway checks token claims (scope, audience, expiry).
- If valid, it injects the Function Key into the backend request.
- This ensures only the gateway can call the Function App.
- Function App Protection
- Function App is private (VNet + private endpoint).
- Requires Function Key for every request.
- Rejects any request without the key.
- Separation of Concerns
- OAuth protects the client-to-gateway path.
- Function Key protects the gateway-to-function path.
Security Benefits
- Zero key exposure — Function Key never leaves the gateway.
- Defense in depth — OAuth + Function Key + Private Endpoint.
- Centralized policy control — Gateway can enforce rate limits, logging, IP restrictions.
- Auditable — All calls logged at the gateway.
Example Azure API Management Policy
XmlCopy code<policies>
<inbound>
<base />
<!-- Validate OAuth Token -->
<validate-jwt header-name="Authorization" failed-validation-httpcode="401" failed-validation-error-message="Unauthorized">
<openid-config url="https://login.microsoftonline.com/{tenant-id}/v2.0/.well-known/openid-configuration" />
<required-claims>
<claim name="aud">
<value>api://your-api-app-id</value>
</claim>
</required-claims>
</validate-jwt>
<!-- Inject Function Key -->
<set-header name="x-functions-key" exists-action="override">
<value>{{function-key-secret}}</value>
</set-header>
</inbound>
<backend>
<base />
</backend>
<outbound>
<base />
</outbound>
</policies>
Here:
{{function-key-secret}}is stored securely in APIM Named Values.- JWT validation ensures only authorized clients reach the Function App.