Skip to main content

Provider API Authentication Onboarding Guide

This guide explains how to onboard an external provider for machine-to-machine access to the Provider API. It focuses on Microsoft Entra registration, credential handling, and the exact integration package that must be shared with the provider.

Scope

  • API surface: POST /api/provider/v1/controls
  • Runtime: Data Service (services/data-service)
  • Auth model: OAuth 2.0 client credentials with JWT bearer tokens
  • Identity platform: Microsoft Entra External ID / Entra-compatible issuer

Current Implementation Summary

The Provider API is protected by a dedicated JWT authentication scheme named ProviderJwt and a dedicated authorization policy named ProviderApiPolicy. Provider routes are selected onto that scheme explicitly and are not authenticated with the default internal user-token scheme.

The access token is accepted only when all of the following are true:

  • The token issuer matches ProviderJwt:Authority
  • The audience matches ProviderJwt:Audience
  • The token is not expired
  • The token contains roles=ProviderApi.Access

Implementation references:

  • services/data-service/Program.cs
  • services/data-service/Controllers/ProviderControlsController.cs
  • services/data-service/appsettings.json
  • openapi/provider-controls.openapi.yaml

Use one confidential client application registration per provider.

Why this is the recommended model:

  • Each provider gets isolated credentials
  • Credentials can be rotated independently
  • Access can be revoked per provider without affecting others
  • Audit logs can be tied to a specific client application

For provider integrations, prefer application roles over delegated scopes.

Recommended standard:

  • Primary authorization model: roles=ProviderApi.Access
  • Token flow: OAuth 2.0 client credentials
  • Client authentication: certificate preferred, client secret acceptable for lower environments

Registration Model

There are two application objects involved.

1. Provider API application

This is the application that represents the protected API.

It defines:

  • The Application ID URI used during token acquisition
  • The API identity whose issued aud claim must match runtime validation
  • The app role exposed to calling clients
  • The token issuer/tenant that signs valid tokens

Current audience rule in this repository:

  • ProviderJwt:Audience must match the aud claim issued in provider access tokens
  • The chosen audience model is the API app client ID GUID when Entra emits that GUID in aud
  • The Application ID URI remains the token acquisition identifier used in scope=<app-id-uri>/.default

Current configured role expected by policy:

  • ProviderApi.Access

2. Provider client application

This is the confidential client used by one external provider.

It holds:

  • client_id
  • A client secret or certificate credential
  • Permission to call the Provider API

Each provider should receive a separate client application.

What You Configure Internally

For each new provider, create or assign the following:

  1. A dedicated client application registration for that provider
  2. A client secret or certificate credential
  3. The Provider API application permission assignment
  4. The ProviderApi.Access app role assignment to that client
  5. Environment-specific base URL information for each target environment

You should also record internally:

  • Provider name
  • Owning organization
  • Environment access granted
  • Credential expiry date
  • Rotation owner
  • Technical contact

What You Send To The Provider

Do not send “the app registration” as an abstract concept. Send a concrete integration package.

The provider needs the following values:

Required integration values

  • Authority or issuer base URL
  • OAuth 2.0 token endpoint
  • Client ID
  • Client credential material:
    • client secret, or
    • certificate onboarding instructions
  • API audience / resource identifier
  • Required permission name
  • API base URL
  • Reference documentation for endpoints and payloads

Use a template like this:

Provider API Integration Package

Environment: <target-environment>

Authority:
https://<tenant>.ciamlogin.com/<tenant>/v2.0

Token endpoint:
https://<tenant>.ciamlogin.com/<tenant>/oauth2/v2.0/token

Client ID:
<provider-client-id>

Client authentication:
Client secret provided through secure channel

API audience:
Use the API app client ID GUID (the value emitted in the token `aud` claim).

Token scope:
Use the Provider API Application ID URI with `/.default`.

Required application permission:
ProviderApi.Access

API base URL:
https://<data-service-host>/api/provider/v1

Endpoints:
POST /controls

Documentation:
documentation/v2/api/16_PROVIDER_API_NOVA_DATA_MAPPING.md
documentation/v2/api/09_PROVIDER_AUTH_ONBOARDING_GUIDE.md

What The Provider Must Do

The provider system must:

  1. Store the client credential securely
  2. Request an access token from the token endpoint
  3. Ask for a token for the configured audience
  4. Call the Provider API with Authorization: Bearer <access_token>

Conceptually, the flow is:

  1. Provider client authenticates to Microsoft Entra
  2. Microsoft Entra issues an access token for the Provider API
  3. Provider sends the token to the Data Service
  4. Data Service validates issuer, audience, lifetime, and permission claims
  5. Request is accepted or rejected

Client Credentials Flow Example

Example token request shape:

POST /<tenant>/oauth2/v2.0/token
Content-Type: application/x-www-form-urlencoded

client_id=<provider-client-id>
client_secret=<provider-client-secret>
grant_type=client_credentials
scope=<provider-api-app-id-uri>/.default

Notes:

  • In client credentials flow, using /.default is the usual pattern
  • The effective claims in the token are derived from app role / application permission assignment
  • The provider should not request user-delegated permissions for this integration
  • Microsoft Entra can issue the API application's client ID as the aud claim even when the token was requested using the Application ID URI in scope.
  • That means the API app client ID GUID is the intended runtime audience value.

Request Example Against The API

curl -X POST "https://<data-service-host>/api/provider/v1/controls" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <provider_access_token>" \
-d @provider-payload.json

Identity Mapping In The Service

The current implementation resolves the provider identity in this order:

  1. azp
  2. appid
  3. client_id
  4. X-Provider-Id
  5. unknown-provider

This means:

  • The real authenticated identity should come from token claims
  • X-Provider-Id must be treated as tracing metadata only
  • It must not be treated as proof of identity

Validation Checklist Before Hand-Off

Before sending credentials to the provider, validate the following:

  1. The Provider API app exposes the expected audience
  2. The provider client has the correct app role assignment
  3. A token can be acquired with client credentials
  4. The token contains the expected roles=ProviderApi.Access claim
  5. The token aud matches ProviderJwt:Audience
  6. The provider client ID is present in ProviderJwt:AllowedClientIds when allowlisting is enabled
  7. A test call to POST /api/provider/v1/controls returns 201 or 200 for duplicates

Verified local development example:

  • Token acquisition scope: api://kontrolleplus-provider-api-dev/.default
  • Issued aud claim: 747deab7-cdf4-4c36-9d77-2ab600fa8743
  • Required role: ProviderApi.Access

This same emitted-audience pattern is the model to apply in each target environment.

Repository validation helper:

  • scripts/test-provider-ingest.sh
  • scripts/validate-provider-jwt.sh
  • scripts/get-and-validate-provider-jwt.sh

Example claim validation before allowlisting:

export PROVIDER_JWT_TOKEN="<provider-access-token>"
export EXPECTED_PROVIDER_AUDIENCE="<provider-api-client-id-guid>"
export EXPECTED_PROVIDER_CLIENT_ID="<provider-client-app-id-guid>"
export EXPECTED_PROVIDER_ROLE="ProviderApi.Access"

./scripts/validate-provider-jwt.sh

One-command token acquisition + validation:

export PROVIDER_TENANT_ID="<tenant-guid-or-domain>"
export PROVIDER_CLIENT_ID="<provider-client-app-id-guid>"
export PROVIDER_CLIENT_SECRET="<provider-client-secret>"
export PROVIDER_TOKEN_SCOPE="api://<provider-api-app-id-uri>/.default"
export PROVIDER_TOKEN_ENDPOINT="https://<tenant>.ciamlogin.com/<tenant>/oauth2/v2.0/token"

export EXPECTED_PROVIDER_AUDIENCE="<provider-api-client-id-guid>"
export EXPECTED_PROVIDER_CLIENT_ID="<provider-client-app-id-guid>"
export EXPECTED_PROVIDER_ROLE="ProviderApi.Access"

./scripts/get-and-validate-provider-jwt.sh

Security Requirements

Use these controls by default:

  • Prefer certificates over shared client secrets in production
  • Deliver secrets through a secure channel only
  • Never send secrets in email or chat
  • Set explicit secret expiry dates
  • Track credential owners and rotation dates
  • Use one client application per provider
  • Revoke access by disabling the client or removing app role assignment when needed

Operational Recommendations

  • One provider = one client application
  • One environment = separate credential set
  • Application role based authorization
  • Certificate-based authentication in production

Avoid

  • Shared credentials across multiple providers
  • Using X-Provider-Id as an identity mechanism
  • Mixing user tokens with provider machine tokens
  • Reusing credentials across environments

Current Authorization Standard

Provider integrations must use application-role based authorization.

Required token claim:

  • roles=ProviderApi.Access

This repository no longer accepts scope-based authorization for provider access.

Provider Hand-Off Template

Use the following short-form message when sending onboarding details to a provider.

Subject: Provider API Access Details

Below are the credentials and endpoints required to integrate with the Kontrolle+ Provider API.

Environment:
<target-environment>

Authority:
<authority-url>

Token endpoint:
<token-endpoint>

Client ID:
<client-id>

Credential delivery:
Provided separately through secure channel

API audience:
<provider-api-client-id-guid>

Token scope:
<provider-api-app-id-uri>/.default

Required permission:
ProviderApi.Access

API base URL:
<provider-api-base-url>

Required header:
Authorization: Bearer <access_token>

Reference documentation:
- documentation/v2/api/16_PROVIDER_API_NOVA_DATA_MAPPING.md
- documentation/v2/api/09_PROVIDER_AUTH_ONBOARDING_GUIDE.md

Provider Token Request Example (Copy/Paste)

Share this exact pattern with providers so they can obtain an access token using client credentials.

TOKEN_RESPONSE=$(curl -sS -X POST "<token-endpoint>" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "client_id=<provider-client-id>" \
-d "client_secret=<provider-client-secret>" \
-d "grant_type=client_credentials" \
-d "scope=<provider-api-app-id-uri>/.default")

ACCESS_TOKEN=$(echo "$TOKEN_RESPONSE" | jq -r '.access_token')
echo "$TOKEN_RESPONSE" | jq '{token_type, expires_in, error, error_description}'

Expected token checks:

  • aud equals <provider-api-client-id-guid>
  • roles contains ProviderApi.Access
  • azp (or appid/client_id) equals <provider-client-id>

Example API call with the token:

curl -X POST "<provider-api-base-url>/controls" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H "Content-Type: application/json" \
-d @provider-payload.json
  • documentation/v2/api/16_PROVIDER_API_NOVA_DATA_MAPPING.md
  • openapi/provider-controls.openapi.yaml
  • services/data-service/Program.cs
  • scripts/test-provider-ingest.sh

Extension: Future Security Hardening Roadmap (ACA)

This section extends the current Provider API Authentication approach with a forward-looking security roadmap. Current behavior remains unchanged unless explicitly implemented.

Current Baseline (Already Implemented)

  • OAuth 2.0 client credentials via Microsoft Entra
  • Provider JWT validation in Data Service (issuer, audience, lifetime, role)
  • Client application allowlisting (ProviderJwt:AllowedClientId / ProviderJwt:AllowedClientIds)

This baseline is valid, but secret theft remains a residual risk.

Target Security Model

  • Azure API Management (APIM) in front of provider endpoints
  • Gateway IP allowlisting for provider egress ranges
  • Mutual TLS (mTLS) for transport-level caller identity
  • Certificate-based client authentication in Entra (replace shared secret)
  • Keep backend JWT checks for defense in depth

Phase Plan

  1. Phase 0: Stabilize current provider auth
  • Keep current JWT checks active.
  • Rotate provider client secrets periodically.
  • Add negative tests: wrong audience, wrong role, wrong client ID, expired token.
  1. Phase 1: Introduce APIM gateway
  • Publish provider routes through APIM only.
  • Add JWT validation policy in APIM.
  • Add rate limiting and quota policy.
  1. Phase 2: Restrict source networks
  • Collect provider outbound IP ranges.
  • Enforce APIM IP allowlist policy.
  • Block all other source IPs.
  1. Phase 3: Add mTLS
  • Require client certificate at APIM custom domain.
  • Validate trusted issuer/thumbprint policy.
  • Map cert identity to provider onboarding record.
  1. Phase 4: Replace secret-based auth
  • Migrate provider clients from secret to certificate credentials.
  • Remove production secret usage from onboarding templates.
  • Enforce credential rotation policy and expiry monitoring.
  1. Phase 5: Operational hardening
  • Add monitoring and alerts for auth failures, IP blocks, mTLS failures, and rate limits.
  • Maintain an incident playbook for credential compromise and rapid revocation.
  • Perform periodic provider access reviews.

Design Principles

  • Keep business code in services/ platform-agnostic.
  • Implement transport and edge controls in infrastructure/gateway layers.
  • Preserve backend claim validation even after gateway controls are introduced.

Suggested Adoption Order

  1. APIM gateway + JWT policy
  2. IP allowlisting
  3. mTLS
  4. Certificate-based Entra client authentication

Exit Criteria Per Phase

  • Phase 1: Provider traffic is routed via APIM and JWT policy is enforced.
  • Phase 2: Only allowlisted provider egress IPs can reach the gateway.
  • Phase 3: Calls require valid client certificate plus JWT.
  • Phase 4: Production providers no longer use client secrets.
  • Phase 5: Monitoring and incident response are tested and documented.