...
- HTTP message handler that intercepts outgoing requests
- Location:
Source/EGU.PartnerPortal.ApiService/Middleware/Handler/ServiceTokenHandler.cs - Automatically gets tokens from Azure ADEntra ID
- Attaches tokens to requests going to IntegrationServiceAPI
3. Microsoft Entra ID (Azure AD Tenant)
...
┌─────────────────────────────────────────────────────────────────┐
│ SERVICE-TO-SERVICE AUTHENTICATION FLOW │
└─────────────────────────────────────────────────────────────────┘
┌──────────────────┐
│ ApiService │
│ (Needs to call │
│ Integration) │
└────────┬─────────┘
│
│ Step 1: Make API call
│ (e.g., send XML message)
▼
┌──────────────────────────┐
│ ServiceTokenHandler │
│ (Middleware) │
└────────┬─────────────────┘
│
│ Step 2: Need token first!
│ Request token from AzureEntra ADID
│ Sends:
│ - Client ID (stored as env. variable in container app)
│ - Client Secret (stored as env. variable in container app)
│ - Scope
▼
┌──────────────────────────────┐
│ Microsoft Entra ID │
│ Token Endpoint │
└────────┬─────────────────────┘
│
│ Step 3: AzureEntra ADID validates
│ ✓ Client ID exists
│ ✓ Client secret matches
│ ✓ Service has permission
▼
┌──────────────────────────────┐
│ Entra Azure ADID Returns Token │
│ (for IntegrationServiceAPI) │
└────────┬─────────────────────┘
│
│ Step 4: Token attached to request
│ Authorization: Bearer eyJ...
▼
┌──────────────────────────────┐
│ IntegrationServiceAPI │
│ Validates Token │
└────────┬─────────────────────┘
│
│ Step 5: Token validation
│ ✓ Signature valid
│ ✓ Issuer correct
│ ✓ Audience correct
│ ✓ Not expired
▼
┌──────────────────────────────┐
│ ✅ Process Request │
│ Execute API logic │
│ Return Response │
└──────────────────────────────┘
...
| Direction | Client ID (Who's calling) | Client Secret (Who's calling) | Audience (Who's being called) |
|---|---|---|---|
| ApiService → Integration | ApiService ID | ApiService secret | IntegrationServiceAPI ID |
| Integration → ApiService | IntegrationServiceAPI ID | IntegrationServiceAPI secret | ApiService ID |
Why Bidirectional?
ApiService calls IntegrationServiceAPI when:
- Sending work order to external system
- Sending completion notification
- Sending cancellation notice
IntegrationServiceAPI calls ApiService when:
...
Each service authenticates itself when making the call, proving its identity to the other service.
...
Step-by-Step Authentication Process
...
- Request intercepted by ServiceTokenHandler
- Handler recognizes authentication is needed
...
Step 2: ServiceTokenHandler Requests Token
What happens:
- ServiceTokenHandler checks if it has a valid cached token
- If no valid token, requests new one from
...
- Entra ID
- Sends client credentials to
...
- Entra ID token endpoint
Who's involved:
- ServiceTokenHandler
- Microsoft Entra ID
What's sent to
...
Entra ID:
- Client ID: ApiService's application ID (stored as env variable in container app)
- Client Secret: ApiService's secret key (stored
...
- as env variable in container app)
- Scope:
.default(all permissions the app has) - Grant Type:
client_credentials
Result:
- Request sent to Azure AD for authentication
...
Step 3:
...
Entra ID Validates Client Credentials
What happens:
...
- Entra ID receives the token request
- Validates the client ID exists in the tenant
- Validates the client secret matches what's registered
- Checks if the app has permission to access IntegrationServiceAPI
Who's involved:
- Microsoft Entra ID
What
...
Entra ID checks:
- ✅ Does this client ID exist?
- ✅ Does the client secret match?
- ✅ Does this app have permission to call IntegrationServiceAPI?
Result:
- If all valid → Proceed to Step 4
- If any invalid → Return error (401 Unauthorized)
...
Step 4: Entra ID Issues Access Token
What happens:
- Entra ID generates an access token
- Token valid for 1 hour
- Token contains app identity (not user identity)
- Token returned to ServiceTokenHandler
Who's involved:
- Microsoft Entra ID
- ServiceTokenHandler
What's in the token:
- Issuer: Microsoft Entra ID
- Audience: IntegrationServiceAPI (who the token is for)
- App Identity: ApiService's application ID
- Permissions: What ApiService can do
- Expiration: 1 hour from now
- Signature: Cryptographic proof of authenticity
Result:
- ServiceTokenHandler receives access token
- Token cached for future requests (1 hour)
...
Step 5: Token Attached to Request
What happens:
- ServiceTokenHandler attaches token to the original request
- Token added as Authorization header:
Bearer {token} - Request continues to IntegrationServiceAPI
Who's involved:
- ServiceTokenHandler
- ApiService
Result:
- Request sent to IntegrationServiceAPI with authentication proof
...
Step 6: IntegrationServiceAPI Validates Token
What happens:
- IntegrationServiceAPI receives request with token
- Authentication middleware examines the token
- Validates token is authentic and valid (using cached Entra ID public keys - no Entra ID call needed)
Who's involved:
- IntegrationServiceAPI
- JWT Authentication Middleware
- Entra ID public keys (cached locally, refreshed every 30 minutes)
What's validated:
- Signature: Proves token came from Entra ID
- Issuer: Confirms it's from the correct Entra ID tenant
- Audience: Ensures it's for IntegrationServiceAPI
- Expiration: Checks it hasn't expired (1-hour lifetime)
- App Permissions: Verifies ApiService has permission
Result:
- ✅ Valid token → Request proceeds to controller
- ❌ Invalid token → Return 401 Unauthorized
...
Step 7: API Processes Request
What happens:
- Request validated successfully
- IntegrationServiceAPI processes the request
- Executes the requested operation
- Returns response to ApiService
Who's involved:
- IntegrationServiceAPI
- ApiService
Result:
- Operation completed
- Response returned to ApiService
- ApiService continues its workflow
...
Token Caching & Reuse
Token Caching
ServiceTokenHandler caches tokens to improve performance:
First request:
- No cached token available
- Request token from Entra ID (takes ~100-200ms)
- Cache token for 1 hour
- Use token for request
Subsequent requests (within 1 hour):
- Check cache for valid token
- Use cached token (takes ~1-5ms)
- No Azure AD call needed
After 1 hour:
- Cached token expired
- Request new token from Entra ID
- Update cache with new token
...
Configuration
ApiService Configuration
What's needed:
| Setting | Description | Example Value |
|---|---|---|
| Client ID | ApiService's application ID | 4dad5d62-dc8c-4378-8bd0-ae736a4d73fe |
| Client Secret | ApiService's secret key | abc123~XYZ789-VerySecret |
| Tenant ID | Azure AD tenant | 6073ce8b-73f3-4df4-9b80-5e40cdc6965f |
| Scope | IntegrationServiceAPI scope | api://bd5100ee-af63-4880-8c60-47d4207d60c1/.default |
Where configured:
- Environment variables (in apiservice container app)
appsettings.json(development only, with secrets.json)
...
IntegrationServiceAPI Configuration
What's needed:
| Setting | Description | Example Value |
|---|---|---|
| Client ID | IntegrationServiceAPI's ID | bd5100ee-af63-4880-8c60-47d4207d60c1 |
| Client Secret | IntegrationServiceAPI's secret key | xyz789~ABC123-VerySecret |
| Tenant ID | Azure AD tenant | 6073ce8b-73f3-4df4-9b80-5e40cdc6965f |
| Scope | ApiService scope | api://4dad5d62-dc8c-4378-8bd0-ae736a4d73fe/.default |
Where configured:
- Environment variables (in integrationservice container app)
appsettings.json(development only, with secrets.json)
...
Quick Reference
ApiService
| Property | Value |
|---|---|
| Client ID | 4dad5d62-dc8c-4378-8bd0-ae736a4d73fe |
| Needs Client Secret | ✅ Yes (calls IntegrationServiceAPI and validates tokens) |
| Tenant ID | 6073ce8b-73f3-4df4-9b80-5e40cdc6965f |
...
IntegrationServiceAPI
| Property | Value |
|---|---|
| Client ID | bd5100ee-af63-4880-8c60-47d4207d60c1 |
| Needs Client Secret | ✅ Yes (calls ApiService and validates tokens) |
| Tenant ID | 6073ce8b-73f3-4df4-9b80-5e40cdc6965f |