Lab 4
API Gateway: Azure APIM with Dual-IdP JWT Validation
👱 Zero Trust Principle: Assume Breach — the API Gateway is the single enforcement perimeter. It validates every token cryptographically, normalizes claims from both Okta and Entra into a common header schema, and backends never see raw IdP tokens.
Architecture: What You Will Build
Your App (localhost:3000)
│
│ Bearer JWT from Okta OR Entra
│
▼
Azure API Management (cloud)
│
├─ 1. Strip X-User-* headers (prevent spoofing)
├─ 2. Detect issuer (Okta or Entra)
├─ 3a. Validate Okta JWT via Okta JWKS
│ Map groups[] → X-App-Roles
├─ 3b. Validate Entra JWT via Entra JWKS
│ Map roles[] → X-App-Roles
├─ 4. Inject: X-User-Id, X-User-Email, X-App-Roles, X-Idp-Source
└─ 5. DELETE Authorization header
│
▼
Express API Server (localhost:4000)
Reads only X-App-Roles — never sees raw token Part A — Create Azure APIM Instance
Step 1: Log in to Azure Portal
Go to: https://portal.azure.com
Step 2: Create a Resource Group
- In the top search bar, type Resource groups
- Click + Create
- Fill in details and click Review + create → Create
Step 3: Search for API Management
- In the top search bar, type API Management
- Click API Management services
- Click + Create
Step 4: Fill in the APIM creation form
| Field | Value |
|---|---|
| Resource name | authmatrix-apim (must be globally unique) |
| Organization name | AuthMatrix Lab |
| Pricing tier | Developer or Consumption |
Click Review + create → Create.
⚠️ Cost warning: Developer tier costs approximately $50/month. Use the Consumption tier for a pay-per-call model.
Step 5: Wait for deployment to complete
When you get the notification "Deployment succeeded", click Go to resource.
Part B — Create the API Definition in APIM
Step 6: Create a new API
- In your APIM instance left sidebar, click APIs
- Click + Add API
- Select HTTP (manual definition)
- Fill in the details:
- Display name:
AuthMatrix Resource Server - Web service URL:
http://host.docker.internal:4000or your ngrok URL - API URL suffix:
api
- Display name:
Click Create.
Step 7: Add API operations
Click + Add operation for each endpoint:
| Operation | Method | URL Template | Display Name |
|---|---|---|---|
| 1 | GET | /protected/me | Get My Identity |
| 2 | GET | /protected/reports | Get Reports |
| 3 | POST | /protected/reports | Create Report |
| 4 | GET | /protected/users | List Users |
| 5 | POST | /protected/users | Create User |
| 6 | GET | /protected/audit | Get Audit Log |
| 7 | DELETE | /protected/audit | Purge Audit Log |
| 8 | PUT | /protected/settings | Update Settings |
For each: fill in Display name and URL, then click Save.
Part C — Apply the Dual-IdP Inbound Policy
Step 8: Open the All Operations policy editor
- In your API, click All operations in the left list
- Click the Policies tab
- Click Policy code editor
Step 9: Paste the dual-IdP policy
Open the file at config/azure-apim-policy.xml in this repo. Paste its full content into the APIM policy editor.
Step 10: Customize the policy for your tenants
In the policy XML, replace the placeholder values (e.g., your Okta domain, your Azure Tenant ID) and click Save.
Part D — Update the Express API to Trust Gateway Headers
Step 11: Add gateway header mode to the auth middleware
Add this new endpoint to apps/api-server/src/index.ts:
// —— APIM GATEWAY MODE: Read pre-validated headers from APIM ——————————————————
app.get('/api/gateway/me', (req, res) => {
const userId = req.headers['x-user-id'];
const email = req.headers['x-user-email'];
const roles = (req.headers['x-app-roles'] as string || '').split(',').filter(Boolean);
const idpSrc = req.headers['x-idp-source'];
if (!userId) {
return res.status(401).json({ error: 'Missing X-User-Id header — request must come via APIM' });
}
res.json({
message: '✅ Identity verified via Azure APIM gateway',
identity: { userId, email, roles, idpSource: idpSrc },
});
}); Step 12: Restart the API server
npm run dev --workspace=apps/api-server Part E — Test the APIM Gateway Flow
Step 13: Get a real token from your IdP
Use the AuthMatrix dashboard to sign in with Okta or Entra. Get the access token.
Step 14: Test APIM using curl
curl -H "Authorization: Bearer YOUR_OKTA_OR_ENTRA_TOKEN" \
https://authmatrix-apim.azure-api.net/api/protected/me Step 15: Test Okta token then Entra token through the SAME APIM gateway
- Get an Okta access token → send to APIM → observe
X-Idp-Source: okta - Get an Entra access token → send to APIM → observe
X-Idp-Source: entra
The backend API receives identical X-App-Roles: Admin regardless of which IdP authenticated the user. The backend is fully IdP-agnostic.
Step 16: Test unauthorized access
curl https://authmatrix-apim.azure-api.net/api/protected/me
# Should get 401 from APIM Part F — APIM Policy Debugging
Step 17: Enable APIM tracing
- In the Azure Portal → your APIM → APIs → AuthMatrix Resource Server
- Click Test tab
- Select GET /protected/me operation
- Add Authorization header and click Trace
✅ Lab 4 Complete — Validation Checklist
- APIM instance created and deployed
- API operations defined for all AuthMatrix endpoints
- Dual-IdP policy XML applied from
config/azure-apim-policy.xml - Policy customized with your Okta domain and Entra Tenant ID
- Express API server reachable from APIM (via ngrok or App Service)
- Okta token sent through APIM → 200 response
- Entra token sent through APIM → 200 response
- Tampered token → 401 from APIM