After Lab 4.3 — Recap
After Lab 4.3 — Recap
You traced requests through the full stack using correlation IDs. You saw how the public backend, internal backend, and Key Vault each contribute to the telemetry chain, and how a single request can be followed from frontend to data layer.
What You Should Understand
The Correlation ID Chain
One ID. Multiple services. Multiple tables. One investigation.
The correlation ID (e.g., 01111111-1111-4111-b111-111111111113) appears in:
- The
AppRequeststable (OpenTelemetry-based SDK data from public and internal backends) - The
AzureDiagnosticstable (HTTP logs from App Services) - The
AzureDiagnosticstable (Key Vault audit logs) - The
AzureDiagnosticstable (Storage audit logs)
With one KQL query, you can see the entire request chain:
union with=source AppTraces, AppRequests
| where TimeGenerated > ago(1h)
| where Message has "01111111-1111-4111-b111-111111111113"
or Name == "/api/data-demo"
| project Source, TimeGenerated, Name, Message
| order by TimeGeneratedStructured vs. Free-Form Logging
| Aspect | Structured (JSON) | Free-Form |
|---|---|---|
| Querying | where event == "secret-accessed" | where message contains "secret" |
| Fields | Fixed schema, predictable columns | Varies per developer |
| Machine-readable | Yes (alerts, dashboards, SIEM) | No (human-readable only) |
| Log volume | Lower (no redundant text) | Higher (redundant context) |
| Search reliability | High (exact field match) | Low (regex, fuzzy matching) |
What the OpenTelemetry-based SDK Captures Automatically
| Auto-collected | Where to query | Security relevance |
|---|---|---|
| HTTP requests | AppRequests table | Detect unusual request patterns, error rates |
| Dependency calls | AppDependencies table | Track calls to Key Vault, databases, external APIs |
| Exceptions | AppExceptions table (when SDK captures them) | Detect application errors, stack traces |
What You Need to Instrument Manually
| Custom event | Where it appears | Why it matters |
|---|---|---|
| Key Vault secret access | AppTraces table (via log() calls) | Detect unauthorized secret enumeration |
| Authentication events | AppTraces table (via log() calls) | Detect failed auth attempts |
| Security-relevant actions | AppTraces table (custom dimensions) | Detect business-level security events |
| Correlation ID propagation | AppTraces table (custom dimensions) | Enable cross-service request tracing |
Reflection Questions
If you don't send an `x-correlation-id` header, what happens?
The backend generates one. The correlation ID middleware checks for the incoming header, and if none is present (or if it fails validation), it generates a UUID and uses that as the correlation ID. The generated ID is then:
- Stored on the request object for use in log entries
- Added to the response header (so the client sees it)
This means every request gets a correlation ID — whether the client sends one or not. The key is that all logs for the same request use the same ID.
Why is the correlation ID passed as an HTTP header rather than embedded in the request body?
Headers are available before the body is parsed:
- HTTP headers are processed by the web server before the request body is read
- This means the correlation ID is available in the earliest possible log entry (request received)
- If the request body is malformed or too large, the body may not be parseable, but headers always are
- Headers are standard for request-level metadata (cookies, authentication tokens, etc.)
Security angle: A malicious request body could be designed to break your parser. The correlation ID in the header is parsed independently, ensuring every request gets an ID regardless of body content.
What security events will you miss if the OpenTelemetry-based SDK is not installed?
All application-level telemetry:
- Dependency calls: You won't see that the application called Key Vault, the database, or an external API
- Custom log events: Security events like "secret accessed" or "auth failed" are not captured
- Exception details: Stack traces and error messages are not captured
- Request details: You won't see the full request lifecycle (start time, duration, status code)
- Performance data: Response times, CPU, memory — all missing
What remains: Only platform-level logs from Diagnostic Settings (HTTP logs, console output) are available. These show that a request arrived and was responded to, but not what the application did with it.
How would you detect if an attacker sent the same correlation ID across multiple requests?
Query for correlation IDs that appear in suspicious patterns:
// Find correlation IDs used more than 5 times
AppTraces
| where TimeGenerated > ago(1h)
| summarize count(), min(TimeGenerated), max(TimeGenerated) by tostring(Properties['x-correlation-id'])
| where count_ > 5
| order by count_ descWhy this matters: A legitimate user generates one correlation ID per request. An attacker may reuse the same correlation ID across multiple automated requests (e.g., a scanning tool that always sends the same header). A correlation ID appearing in dozens of requests from the same IP is a strong signal of automated scanning or attack. Note that if the client does not send an x-correlation-id header, the backend generates one automatically — so you won't see this pattern unless the attacker explicitly sets the header to the same value each time.
Common Issues
| Issue | Symptom | Cause | Fix |
|---|---|---|---|
| Correlation ID not appearing in logs | Query by correlation ID returns 0 rows | Middleware not mounted before routes, or not initialized | Verify correlationIdMiddleware is mounted before routes in app.ts |
| Different services using different ID names | Cannot join logs across services | One service uses correlationId, another uses x-correlation-id | Standardize on x-correlation-id header name across all services |
| SDK data delayed | Request appears in SDK tables 5+ minutes later | SDK batching or network latency | Use ago(15m) time filter in queries, or generate more traffic to increase data volume |
| Correlation ID in response header but not in logs | Response has the ID but logs don't | Logger not receiving correlation ID from middleware | Pass correlationId to the logger: logger.log({ correlationId, ... }) |
| Key Vault access not showing in AppDependencies | No dependency call for Key Vault | Azure SDK client not using OpenTelemetry-based SDK | Verify @azure/keyvault-secrets is initialized after useAzureMonitor() and the SDK connection string is configured |
Bridge to Lab 4.4
You have traced requests through the full stack and verified that telemetry flows from frontend to data layer. Now you have the data — the next step is to enable Microsoft Sentinel on top of Log Analytics to turn collected logs into security operations workflows.