- What: Azure Function configured to only accept requests from APIM IP
- APIM IP:
48.217.203.230 - Result: Direct access to Function blocked at network level
- What: APIM sends secret header, Function validates it
- Header:
X-APIM-Secret - Result: Requests without valid secret get 403 Forbidden
- What: Function validates IMS headers forwarded by APIM
- Headers:
X-IMS-User-Id,X-IMS-Email,X-User-Tier - Result: Requests without IMS context get 401 Unauthorized
- What: Monthly token limits enforced per user tier
- Limits: Premium (1M), Standard (100K), Free (10K) tokens/month
- Result: Requests exceeding quota get 429 Quota Exceeded
- What: All queries logged to Azure Table Storage
- Tables:
TokenUsage(per-query),MonthlyQuotas(monthly totals) - Result: Complete audit trail for compliance and analytics
# Get fresh IMS token
aio config set cli.env stage
aio auth login
TOKEN=$(aio auth login --bare)
# Test query
curl -X POST https://commerce-docs-dev-apim.azure-api.net/api/query \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{"query": "webhooks", "count": 2}' | jq '.'Expected Result: ✅ Success with usage info
{
"success": true,
"results": [...],
"usage": {
"tokensUsed": 1234,
"tokensRemaining": 998766,
"monthlyLimit": 1000000,
"tier": "premium",
"percentUsed": 0
}
}curl -X POST https://commerce-docs-dev-api.azurewebsites.net/api/query \
-H "Content-Type: application/json" \
-d '{"query": "test"}' | jq '.'Expected Result: ❌ 403 Forbidden (IP restriction)
{
"success": false,
"error": "Forbidden",
"message": "Direct access not allowed. Please use the API gateway."
}curl -X POST https://commerce-docs-dev-apim.azure-api.net/api/query \
-H "Content-Type: application/json" \
-d '{"query": "test"}' | jq '.'Expected Result: ❌ 401 Unauthorized (APIM blocks)
{
"error": "missing_token",
"message": "IMS token required. Please include Authorization: Bearer <token> header."
}# Query Azure Table Storage to see logged usage
az storage entity query \
--table-name TokenUsage \
--connection-string "$(az storage account show-connection-string \
--name commercedocsdevstorage \
--resource-group commerce-docs-dev-rg \
--query connectionString -o tsv)" \
--select PartitionKey,RowKey,timestamp,query,tokensUsed,tier \
--top 5 \
--output tableExpected Result: ✅ See recent queries logged
# Query MonthlyQuotas table
az storage entity query \
--table-name MonthlyQuotas \
--connection-string "$(az storage account show-connection-string \
--name commercedocsdevstorage \
--resource-group commerce-docs-dev-rg \
--query connectionString -o tsv)" \
--select PartitionKey,RowKey,tokensUsed,email,tier \
--output tableExpected Result: ✅ See quota usage per user
- Test 1: Valid APIM request works ✅
- Test 2: Direct Function access blocked ❌
- Test 3: Missing IMS token blocked ❌
- Test 4: Usage tracked in TokenUsage table ✅
- Test 5: Quotas updated in MonthlyQuotas table ✅
- Verify IP restriction in Azure Portal
- Verify shared secret configured in both APIM and Function
- Verify APIM policy includes X-APIM-Secret header
- Verify Function validates all security layers
az storage entity query \
--table-name TokenUsage \
--connection-string "$(az storage account show-connection-string \
--name commercedocsdevstorage \
--resource-group commerce-docs-dev-rg \
--query connectionString -o tsv)" \
--filter "timestamp ge datetime'$(date -u -v-1H +%Y-%m-%dT%H:%M:%SZ)'" \
--output tableaz storage entity query \
--table-name MonthlyQuotas \
--connection-string "$(az storage account show-connection-string \
--name commercedocsdevstorage \
--resource-group commerce-docs-dev-rg \
--query connectionString -o tsv)" \
--output tableaz monitor app-insights query \
--app commerce-docs-dev-insights \
--resource-group commerce-docs-dev-rg \
--analytics-query "traces | where timestamp > ago(1h) | where message contains 'Query function' | project timestamp, message | order by timestamp desc" \
--output table- Check Function logs for suspicious activity
- Review TokenUsage table for unusual patterns
- Verify IP restrictions are in place
- Rotate APIM shared secret if compromised
- Review APIM analytics for blocked requests
# Generate new secret
NEW_SECRET=$(openssl rand -base64 32)
# Update Function App
az functionapp config appsettings set \
--name commerce-docs-dev-api \
--resource-group commerce-docs-dev-rg \
--settings "APIM_SHARED_SECRET=$NEW_SECRET"
# Update APIM
az apim nv update \
--service-name commerce-docs-dev-apim \
--resource-group commerce-docs-dev-rg \
--named-value-id "apim-shared-secret" \
--value "$NEW_SECRET" \
--secret true- Security: 100% of requests validated through all layers
- Availability: 99.9% uptime with proper error handling
- Performance: < 3s P95 latency (including security checks)
- Compliance: Complete audit trail in Table Storage
- User Experience: Clear error messages and usage info