Skip to content

API authentication assurance and step-up ​

An API can require stronger or more recent user authentication for a particular operation. For example, viewing an order may accept a password login, while approving payment requires MFA performed within the last five minutes. The API decides these requirements; ProAuth supplies verified evidence and performs authentication when the application requests it.

This feature is explicitly enabled per client, tenant and exact API audience. Existing integrations retain their behavior when there is no matching enabled policy. A policy does not grant scopes, roles, access to a tenant, or token-exchange permission.

Tenant/IdP routing and language preferences remain separate from assurance. Use dedicated proauth_* parameters and standard ui_locales in new integrations. Recognized legacy routing tokens in acr_values remain supported and are excluded from step-up requirements. See authorization parameters and legacy routing.

Configure the client policy ​

In the Admin UI, open Client application → Permissions → API authentication assurance. Select a tenant assigned to the client and enter an allowed target audience. Audience matching is case-sensitive and exact; wildcards are unsupported.

SettingBehavior
Disabled (default)Existing token disclosure and ordinary OIDC behavior.
Evidence onlyAdds server-verified acr and numeric auth_time to user access tokens for this audience.
Evidence and step-upAlso treats requested assurance acr_values as requirements under RFC 9470. Unmet requirements initiate authentication or terminate with unmet_authentication_requirements.
Include authentication methodsAdds the optional amr array. Defaults to false.

Use one audience in an enabled assurance request. A request combining that audience with other audiences fails with invalid_target. Unconfigured requests keep their existing audience behavior. Policies have a unique client-local name and a unique client/tenant/audience combination, including disabled policies. Names have a 100-character limit; audiences have a 400-character limit.

The v2 management API supports CRUD, PATCH, auditing and ETag concurrency at /api/management/v2/ClientAppAuthenticationAssurancePolicy. Reading assurance policies requires ClientApp.Read within the client's scope. The generated v2 service clients expose CreateClientAppAuthenticationAssurancePolicyAsync and related operations. V1 has no assurance-policy surface.

Example create body:

json
{
  "clientAppId": "11111111-1111-1111-1111-111111111111",
  "tenantId": "22222222-2222-2222-2222-222222222222",
  "name": "Payments",
  "audience": "https://api.example.com/payments",
  "mode": "stepUp",
  "includeAuthenticationMethods": false
}

YAML import uses an owned collection in the existing ClientApp resource. Use stable IDs so repeated imports update the same policy:

yaml
apiVersion: management.proauth.io/v2
kind: ClientApp
metadata:
  id: 11111111-1111-1111-1111-111111111111
spec:
  # Include the other client settings required by your provisioning document.
  authenticationAssurancePolicies:
    - id: 33333333-3333-3333-3333-333333333333
      name: Payments
      tenantId: 22222222-2222-2222-2222-222222222222
      audience: https://api.example.com/payments
      mode: stepUp
      includeAuthenticationMethods: false

Evidence and lifetime ​

acr describes an authentication context actually satisfied by the user. API tokens use a canonical ProAuth context, resolving any configured tenant alias selected for the ID token. Use canonical values in API requirements. Supported contexts and their prerequisites are described in passkey assurance. An API explicitly lists acceptable contexts; the helper does not infer a numeric ranking from their names.

auth_time is the original user-authentication time in Unix seconds. It is not the token issue time (iat). amr, when requested by the policy, describes verified authentication methods and is always an array. Claim transformations, incoming request values and upstream provider assertions cannot raise these values. Machine-only grants do not acquire user authentication evidence.

Refresh and supported internal token exchange preserve the source authentication time and evidence. They cannot satisfy a requirement for a new login. Exchange also requires the existing exchange policy, scope and sender-binding checks, plus an assurance policy for the receiving client and destination audience. Tokens issued before authoritative evidence was recorded may need a new interactive login.

Introspection returns only evidence present in the actual access token, permitted by the issuing client's current policy, and only to an authorized introspection caller. Disabling a policy stops introspection disclosure and subsequent opt-in issuance; it cannot remove claims from JWTs already held by applications. Use normal revocation and token lifetimes when immediate withdrawal is needed.

Require assurance in an API ​

Use ProAuth.Oidc.ResourceServer.AspNetCore to validate issuer, signature or introspection, audience, lifetime and sender binding. The validated access token must supply the required acr and auth_time, and the current request principal must still satisfy the requirement after claims transformation. After normal endpoint authorization, check assurance before changing business state:

csharp
using ProAuth.Oidc.Common.Assurance;
using ProAuth.Oidc.ResourceServer.AspNetCore.Assurance;

var requirement = new AuthenticationAssuranceRequirement(
    ["https://proauth.net/acr/mfa", "phr", "phrh"],
    MaximumAgeSeconds: 300);

var challenge = httpContext.RequireAuthenticationAssurance(requirement);
if (challenge is not null) return challenge;

// Recheck transaction state and perform the authorized operation.

The helper requires successful authentication by the ProAuth resource-server handler. Insufficient, missing, duplicated, malformed or future-dated evidence does not satisfy the requirement. It returns HTTP 401 with a Bearer or DPoP challenge, matching the validated request:

http
WWW-Authenticate: Bearer error="insufficient_user_authentication", acr_values="https://proauth.net/acr/mfa phr phrh", max_age="300"
Cache-Control: no-store

Ordinary permission failures should remain authorization failures; stronger authentication does not supply missing permissions. max_age is in seconds. Use a positive age allowance for an API operation so normal browser and network time can elapse.

Handle a challenge in the application ​

ProAuth.Oidc.Client.Assurance.StepUpChallenge.Read(response) parses an unambiguous RFC 9470 challenge from a 401 response. It does not navigate, retry or trust the responding destination for you. Only act on challenges from the application's configured trusted API. Match the challenge to an operation the application understands.

The BFF supports named policies under its existing ProAuthBff configuration:

json
{
  "ProAuthBff": {
    "StepUpPolicies": {
      "approve-payment": {
        "Audience": "https://api.example.com/payments",
        "AcceptedAcrValues": ["https://proauth.net/acr/mfa", "phr", "phrh"],
        "MaximumAgeSeconds": 300
      }
    }
  }
}

The BFF policy Audience must be a well-formed absolute resource URI without a fragment, such as https://api.example.com/payments or urn:example:payments. Relative paths are rejected.

An empty policy dictionary is the default. To begin one deliberate attempt, submit an authenticated form POST to /bff/account/step-up with policy=approve-payment, a local returnUrl, and a valid ASP.NET Core antiforgery token. Use the host application's established antiforgery integration and include its PathBase when building the action URL. The browser cannot supply arbitrary audience or assurance requirements; the policy name selects server configuration.

The BFF sends resource, acr_values, max_age and essential ID-token assurance claims through its existing authorization-code flow, including PAR/JAR when configured. It binds continuation to protected OIDC state, the original subject and issuer, and a ten-minute attempt window. The session retains the issuer from the validated ID token for this check; UserInfo data cannot replace that issuer. It validates the returned ID-token evidence before storing replacement tokens. A failed attempt leaves the original BFF session in place. Normal OIDC correlation, state, nonce and PKCE checks continue to apply.

The endpoint initiates one attempt. There is no automatic retry or storage/replay of an API request body. On return, the application checks the result, reloads current business state and asks the user to confirm a sensitive write again. Cancellation, unavailable authentication methods and unmet requirements must lead to a clear failure state; the application must not automatically start another challenge. Configure the tenant's actual authentication and MFA methods beforehand: enabling a policy does not enroll a factor or make an unsupported authentication method available.

Standards ​

This combines RFC 9470 step-up challenges, optional authentication claims in the RFC 9068 JWT access-token profile, RFC 7662 introspection, and OIDC authentication-context and freshness requests. Ordinary OIDC acr_values preference semantics remain unchanged outside StepUp policies.