Implements the shared contract with one dispatch engine and one selected provider per deployment. Target: .NET 8 isolated worker, Azure Functions v4.
- Follow customer onboarding. Set
EPP_PROVIDER_NAMEto the selected adapter's registered manifest id (<adapter-id>is only a placeholder). - Consult the selected adapter and its manifest in Src/Providers/ for required credentials and options. Store credentials in Key Vault under the declared secret names, grant the Function's managed identity Key Vault Secrets User, and configure the matching endpoint/options.
- Base private local settings on ../docs/local.settings.sample.json,
replacing placeholders and selecting
FUNCTIONS_WORKER_RUNTIME=dotnet-isolated. Put settings at the app root beside host.json. Configure decryption from the shared catalog and caller trust through Easy Auth, the only authentication gate before the anonymous Function. EnablerequireAuthentication=true,unauthenticatedClientAction=Return401andrequireHttps=true; pin the trusted tenant issuer, endpoint-appallowedAudiencesand a nonemptyallowedApplicationslist for the authorized SAS caller. Do not exclude SendOtp. There is no backup application token validation; never expose the endpoint to the public internet with Easy Auth disabled or bypassed. - Build dotnet.csproj, run the offline xUnit suites in tests/Epp.Otp.Tests.csproj, and start the local Functions host from this folder. Core Tools has no Easy Auth: bind only to loopback, with no tunnels or public forwarding.
- Publish this app folder to a compatible .NET isolated Function App. Inspect the package: both .funcignore and the project protect local settings; private keys must not be included. Offline tests cover application behavior, not platform authentication; run the separate deployed security checks.
Run Core Tools from dotnet/. Create an untracked local.settings.json beside
host.json, starting from the shared sample.
For local evaluation, start Azurite and replace the test-key placeholder in this minimal setup:
{
"IsEncrypted": false,
"Values": {
"AzureWebJobsStorage": "UseDevelopmentStorage=true",
"FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated",
"EPP_DECRYPTION_KEY_PEM": "<base64 of your local test private PEM>"
}
}For live delivery, add EPP_PROVIDER_NAME, EPP_PROVIDER_ENDPOINT and KEY_VAULT_URL to Values.
Add EPP_PROVIDER_ACCOUNT_NAME and adapter-specific options only when required. Optional
EPP_PROVIDER_TIMEOUT_MS is a string such as "1500". Replace placeholders; store provider credentials
under the adapter manifest's Key Vault secret names, not in local settings. See the
complete variable table.
Core Tools loads Values into environment variables. AppConfig.Read reads them
through IEnv; direct worker execution and unit tests do not automatically load local settings.
Restart the host after edits. Configure local host storage other than Azurite separately; do not copy
the emulator connection into Azure. Core Tools does not resolve Key Vault references locally; supply
the local test PEM or base64 PEM directly. The project excludes private local settings
from publish output.
For Azure, configure the same application variables on the serving app/slot's Environment variables → App settings page and resolve the private PEM through a Key Vault reference. Key Vault provider credentials use managed identity, not the developer's CLI login. Use loopback-only local evaluation or the offline tests' injected environment and secret resolver for local development.
POST /api/SendOtp uses the same request and trust boundaries as the other runtimes. Incoming
mode, channel, ttlSeconds and tenantId are request data, not deployment authentication settings.
Easy Auth authenticates and authorizes the caller before the anonymous handler validates the envelope
and decrypts the JWE. Incoming Authorization is not parsed or echoed by the handler. JWE does not
authenticate SAS: anyone with the public key can encrypt a request, and a fixed nonce is not authentication.
Use incoming mode: 2 or mode: "evaluation" as the generic shutter for every provider: platform
authentication on Azure, handler validation and decryption run, but provider lookup, provider Key Vault
reads and provider HTTP do not. No provider configuration or diagnostic environment flag is required.
Live requests forward the rendered message unchanged using the configured provider's API key and
await acceptance before returning the nonce; failures omit it. Acceptance is not handset delivery.
Platform/key prerequisites and HTTP outcomes are defined in the
contract.
| Source | Purpose |
|---|---|
| Program.cs | Host and adapter registration |
| Functions/SendOtp.cs | HTTP handler |
| Src/AppConfig.cs | Shared deployment settings |
| Src/DispatchEngine.cs | Envelope/JWE handling and dispatch |
| Src/ProviderRegistry.cs, Src/IProviderAdapter.cs | Adapter lookup and contract |
| Src/Providers/ | Adapter manifests and API-specific implementations |
| Src/SecretResolver.cs | Cached Key Vault access via managed identity |
| Src/OutcomeMapper.cs, Src/Models.cs | Outcomes and shared records |
Implement IProviderAdapter and register it in Program.cs without adding provider-specific
branches to the shared pipeline. See production limitations before production use.