Subscription Key Authentication¶
Configure APIM subscription key authentication for Experience APIs that serve legacy server-to-server clients.
| Property | Value |
|---|---|
| Goal | Allow legacy applications to call your API using an APIM subscription key |
| Prerequisites | Experience API deployed (is_experience_api: true), variable group with keys |
| Time Estimate | 15 minutes |
| Difficulty | Beginner |
Overview¶
Some legacy applications cannot obtain OAuth tokens and need a simpler authentication mechanism. The subscription_key policy type configures your Experience API to accept APIM subscription keys instead of JWT tokens.
The pipeline handles the full lifecycle:
- Terraform creates the APIM subscription using your keys from the variable group
- Crypto registration (opt-in) stores the keys in the Crypto API and records cipher IDs in the Environment Service so legacy .NET apps can retrieve them via
_environmentClient.GetSecureSetting()
When to Use¶
| Scenario | Recommended Policy Type |
|---|---|
| Standard user-delegated or service-to-service | standard |
| Filevine webhook integration | filevine |
| Legacy app that cannot use OAuth | subscription_key |
Use Only When Necessary
Subscription key auth provides weaker security guarantees than OAuth. It does not carry user identity or granular scopes. Only use this when the calling application cannot support OAuth flows.
How It Works¶
sequenceDiagram
participant Legacy App
participant Front Door
participant APIM
participant Backend API
Legacy App->>Front Door: GET /api/exp/your-app/<br/>Ocp-Apim-Subscription-Key: {key}
Front Door->>APIM: Forward request + X-Azure-FDID header
APIM->>APIM: Validate subscription key
APIM->>APIM: Validate Front Door ID
APIM->>APIM: Set x-forward-tenant-origin: corp
APIM->>Backend API: Forward with managed identity token
Backend API-->>APIM: Response
APIM-->>Front Door: Response
Front Door-->>Legacy App: Response
Key differences from standard policy:
- No JWT validation — APIM validates the subscription key natively
- No scopes or roles — all requests are treated as corporate internal
- Managed identity — APIM still authenticates to the backend using managed identity
Step 1: Configure vars.yml¶
In your Experience API's vars.yml, set api_policy_type to subscription_key:
# infra/api/vars.yml
application_name: your-app-name
application_type: Api
api_type: Experience
owner: YourTeam
project_id: your-project-id
api_policy_type: subscription_key
Experience APIs Only
The subscription_key policy type requires api_type: Experience (which sets is_experience_api: true). Terraform validation will fail if you use it with other API types.
Step 2: Add Subscription Keys to Variable Group¶
Store your subscription keys in an Azure DevOps variable group so the pipeline can pass them to Terraform and (optionally) register them in the Crypto API.
Generate Keys¶
Use a password generator (e.g., Keeper) to create a unique key for each environment. Requirements:
- Letters, numbers, hyphens (
-), and underscores (_) only — avoid all other symbols (the pipeline is sensitive to special characters) - Minimum 32 characters, maximum 256
- Different key per environment — do not reuse the same key across TEST, QA, UAT, and PROD
Add Keys to Variable Group¶
- Navigate to Azure DevOps → Pipelines → Library
- Open your application's variable group (the name must match
secretsVariableGroupNamein your pipeline — typically your project ID, e.g.,it-api-exp-yourapp) - Add environment-prefixed entries and mark each as Secret:
| Variable Name | Type | Description |
|---|---|---|
[TEST]primary_key |
Secret | Primary key for TEST |
[QA]primary_key |
Secret | Primary key for QA |
[UAT]primary_key |
Secret | Primary key for UAT |
[PROD]primary_key |
Secret | Primary key for PROD |
Optionally add secondary_key entries for key rotation support:
| Variable Name | Type | Description |
|---|---|---|
[TEST]secondary_key |
Secret | Secondary key for TEST |
[PROD]secondary_key |
Secret | Secondary key for PROD |
Critical
If you rename or use a different variable group, its name must match the secretsVariableGroupName value in your pipeline YAML file.
Treat Subscription Keys as Secrets
Subscription keys grant full access to your API. Mark them as Secret in the variable group and never commit them to source control.
Step 3: Configure Pipeline¶
Add secretsVariableGroupName to both your main pipeline and PR pipeline:
extends:
template: azure-dotnet-api-v3.yml@templates
parameters:
applicationName: ${{ variables.ApplicationName }}
projectId: ${{ variables.ProjectId }}
dotNetVersion: '10.x'
secretsVariableGroupName: 'your-project-id' # Variable group from Step 2
Enable Crypto Registration¶
If legacy .NET callers need to retrieve the subscription key at runtime via _environmentClient.GetSecureSetting(), also add the cryptoRegistration parameter. This stores the keys in the Crypto API and records the cipher IDs in the Environment Service after each deployment.
extends:
template: azure-dotnet-api-v3.yml@templates
parameters:
applicationName: ${{ variables.ApplicationName }}
projectId: ${{ variables.ProjectId }}
dotNetVersion: '10.x'
secretsVariableGroupName: 'your-project-id'
cryptoRegistration:
enabled: true
systemName: 'your-project-id' # Name of the Crypto system
systemType: 'webapp' # console, windowsservice, webapp, guidewire, desktopapp, script, other
| Parameter | Required | Default | Description |
|---|---|---|---|
enabled |
Yes | false |
Enable crypto registration |
systemName |
Yes | '' |
Name of the system in Crypto API |
systemType |
No | 'webapp' |
System type (console, windowsservice, webapp, guidewire, desktopapp, script, other) |
See Settings and Secrets for full details on the secretsVariableGroupName parameter and the [ENV] naming convention.
Step 4: Deploy¶
Commit and push your changes. The pipeline:
- Loads keys from the variable group (filtered by environment)
- Passes them to Terraform via
application_secrets - Creates the APIM subscription with your keys
- Sets
subscription_required = trueon the APIM API - Applies the subscription key policy (no JWT validation)
If crypto registration is enabled, the pipeline additionally:
- Creates or updates a Crypto system for your application
- Registers each key as an encrypted cipher in the Crypto API
- Stores the cipher IDs as secure settings in the Environment Service
Settings are stored under:
- Track: mapped from the pipeline environment (
platformdev/test→enttest1,qa→np13,uat→np16,prod→prod) - Setting group: your
projectId - Keys:
primary_subscription_key,secondary_subscription_key
You can verify the registered settings in EnvironmentBoss.
Step 5: Call the API¶
Legacy callers must send the subscription key in the Ocp-Apim-Subscription-Key header through Front Door:
GET https://app-int-{environment}.saif.com/api/exp/{app-name}/{route}
Ocp-Apim-Subscription-Key: {your-subscription-key}
Retrieving Keys via Environment Service¶
If crypto registration is enabled, legacy .NET callers can retrieve the decrypted subscription key using the Environment Client and System Hash Service.
Required NuGet package (from the SAIF internal feed):
This package provides IEnvironmentClient, ISystemHashService, and SystemType. Register both services in your DI container and inject them into the class that needs to retrieve keys:
public class SubscriptionKeyProvider
{
private readonly IEnvironmentClient _environmentClient;
private readonly ISystemHashService _systemHashService;
public SubscriptionKeyProvider(
IEnvironmentClient environmentClient,
ISystemHashService systemHashService)
{
_environmentClient = environmentClient;
_systemHashService = systemHashService;
}
public string GetPrimaryKey()
{
// Compute time-sensitive system hash
var hash = _systemHashService.ComputeHash(
SystemType.WebApp, _environmentClient.ApplicationName);
// Retrieve decrypted key — returns the original subscription key value
return _environmentClient.GetSecureSetting(
"primary_subscription_key",
"your-project-id", // setting group = projectId
hash);
}
}
System Hash Timeout
The system hash is time-sensitive (~90 second window). Compute the hash immediately before each GetSecureSetting call.
Key Rotation¶
To rotate subscription keys without downtime:
- Generate a new key value
- Update the
secondary_keyentry in your variable group with the new value - Deploy — APIM and Crypto/Environment Service are updated with the new secondary key
- Distribute the secondary key to all callers
- Once callers have switched, swap: move the new value to
primary_key, generate another forsecondary_key - Deploy again to finalize
Troubleshooting¶
401 SubscriptionKeyInvalid¶
| Cause | Solution |
|---|---|
| Wrong key value | Verify the key matches what's in your variable group for the target environment |
| Missing header | Ensure Ocp-Apim-Subscription-Key header is present and not empty |
| Key not deployed | Confirm the pipeline ran successfully after adding keys to the variable group |
403 Header X-Azure-FDID Not Found¶
| Cause | Solution |
|---|---|
| Calling APIM directly | Requests must go through Front Door, not directly to APIM |
500 BackendConnectionFailure¶
| Cause | Solution |
|---|---|
| Backend app not deployed | Deploy your application before testing |
| Mock backend not configured | Send x-mocking: false header to route to the real backend |
Crypto Registration Failures¶
| Cause | Solution |
|---|---|
primary_key not found in application_secrets |
Add [ENV]primary_key entries to your variable group |
Unknown environment |
Verify DeploymentEnvironmentShortName maps to a known track |
| Crypto API timeout | The Crypto API may need a warm-up — the pipeline retries automatically |
📚 Related Documentation¶
- Settings and Secrets - Variable group setup and the
[ENV]naming convention - Event Service - Same variable group pattern for event service keys
- Authorization Concepts - Scopes, roles, and JWT-based auth
- App Permissions - Standard OAuth app-to-app authorization
- Filevine Webhook Integration - Filevine-specific policy type