Documentation | Settings | Request behavior | Source
Implements the shared contract with one provider per deployment. Target: .NET 8 isolated worker, Azure Functions v4.
Sample deployment: follow the five-step guide, then complete provider authentication and validation.
Custom package development: follow these steps from dotnet/:
- Create the local settings below. Select an adapter's fixed
Namefrom Providers/, configure it, and complete provider authentication. - Build dotnet.csproj, run xUnit tests, and start Core Tools here, on loopback only, without tunnels. It has no Easy Auth.
- For manual deployment, publish to a compatible .NET isolated Function App. Inspect the output for private settings and complete the Easy Auth trust checklist, then run authorized deployed checks to verify platform authentication.
Regional continuity: use the Front Door guide and include .NET-specific package, startup, dependency, and telemetry 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, use the shared settings table
for EPP_PROVIDER_NAME, both channel endpoints, authentication, and adapter-specific options.
Keep values as strings, including EPP_PROVIDER_TIMEOUT_MS, for example "1500".
Replace placeholders and store credentials in the adapter's Key Vault secrets. Each selected
channel requires a valid endpoint. An invalid or missing endpoint returns an error without using
the other channel.
Core Tools loads Values into environment variables. Direct worker execution and tests do not.
Restart after edits. Use a local test PEM because Core Tools cannot resolve Key Vault references.
The project excludes local settings from publish output.
In Azure, configure the same app settings and use a Key Vault reference for the private PEM. Provider credentials use managed identity, not a developer CLI login. Keep local evaluation on loopback.
POST /api/SendOtp follows the shared request/response contract.
Easy Auth authenticates the caller. The handler then validates and decrypts the payload. It never
forwards or returns the incoming Authorization header.
.NET binding runs first: [FromBody] creates EntraSendOtpPayload. Malformed JSON, invalid
enum tokens, or other deserialization errors can fail before SendOtp, its ILogger events,
or its custom error response. Successfully bound requests retain application validation,
privacy-safe logging, and response behavior. See
binding differences.
Use mode: 2 or "evaluation" to validate
authentication and decryption without a provider send. Live success confirms acceptance,
not handset receipt. Check a timeout before retrying.
For built-in message and voice mapping, see Soprano and Telesign.
To add Twilio, follow the Twilio integration guide. Keep caller authentication and public responses in the shared handler.
For deployed diagnostics, use Application Insights and
runtime-specific log discovery.
The host's OpenTelemetry setting does not by itself initialize a direct worker exporter.
verify actual ILogger collection before depending on event-based alerts.
SendOtp validates and decrypts, selects EPP_PROVIDER_NAME, resolves credentials, and calls the
provider. PhoneProviderBase.SendJsonAsync<TResponse> owns bounded HTTP transport and typed response
mapping. Each provider maps its statuses to ProviderResult. Request-aware routing or multiple
accounts require a separate trust, configuration, credential, and test design.
CredentialTokenService invokes the selected provider's credential retrieval for each live
request with a 2.5-second cancellation budget. API-key secrets are read from Key Vault each time.
The OAuth provider reuses Azure Identity SDK credential instances, leaving token caching and
renewal to the SDK. There is no custom credential cache, hosted warmup service, polling, or
retry cooldown. Evaluation does no provider credential work. See
credential acquisition.
| Source | Purpose |
|---|---|
| Program.cs | Host, provider, credential service and HTTP client registration |
| Functions/SendOtp.cs | HTTP handler, provider selection, credential resolution and delivery orchestration |
| Src/AppConfig.cs | Shared deployment settings |
| Src/EntraSendOtpPayload.cs | Bound request model, strict channel/mode converters and semantic validation |
| Src/JweDeliveryContext.cs | Pinned JWE decryption and decrypted delivery context |
| Src/CredentialTokenService.cs | Bounded provider-supplied credential retrieval for each live request |
| Src/OtpLog.cs | Source-generated, strongly typed structured logging events |
| Src/PhoneProviderBase.cs | Provider extension contract and shared typed JSON/HTTP transport |
| Src/Providers/ | Provider identity, credential delegation and API-specific request/response protocols |
| Src/SecretResolver.cs | Key Vault transport. ISecretResolver.ResolveAsync accepts cancellation. |
| Src/Models.cs | Outcomes and shared records |
Derive from PhoneProviderBase, register the provider in Program.cs, and give it the configured
name used by the simple SelectProvider policy in SendOtp. Providers own authentication declaration,
credential resolution, request construction and private typed response DTOs. Their public SendOtpAsync
method delegates to the base SendJsonAsync<TResponse> transport and maps the typed response into
ProviderResult. Provider mapping code never handles HttpResponseMessage or raw JSON DOM types.
Each provider keeps its own response rules. Common async HTTP transport lives in
PhoneProviderBase. See
production readiness when planning a live service.