Source/EGU.PartnerPortal.ApiServiceSource/EGU.PartnerPortal.ApiService/Middleware/Handler/ServiceTokenHandler.cs6073ce8b-73f3-4df4-9b80-5e40cdc6965fSource/EGU.PartnerPortal.IntegrationServiceAPI┌─────────────────────────────────────────────────────────────────┐
│ 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 Entra ID
│ 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: Entra ID validates
│ ✓ Client ID exists
│ ✓ Client secret matches
│ ✓ Service has permission
▼
┌──────────────────────────────┐
│ Entra ID 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 │
└──────────────────────────────┘
The authentication works in both directions:
Direction 1: ApiService → IntegrationServiceAPI
Direction 2: IntegrationServiceAPI → ApiService
The authentication flow is identical in both directions:
Only the credentials differ:
| 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 |
Note: The steps below show ApiService calling IntegrationServiceAPI, but the process is identical in reverse (IntegrationServiceAPI calling ApiService) - just swap the service names and credentials.
What happens:
Who's involved:
Result:
What happens:
Who's involved:
What's sent to Entra ID:
.default (all permissions the app has)client_credentialsResult:
What happens:
Who's involved:
What Azure AD checks:
Result:
What happens:
Who's involved:
What's in the token:
Result:
What happens:
Bearer {token}Who's involved:
Result:
What happens:
Who's involved:
What's validated:
Result:
What happens:
Who's involved:
Result:
ServiceTokenHandler caches tokens to improve performance:
First request:
Subsequent requests (within 1 hour):
After 1 hour:
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:
appsettings.json (development only, with secrets.json)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:
appsettings.json (development only, with secrets.json)| 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 |
| 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 |