Skip to content

High-assurance OAuth does not usually fail because one engineer forgot the name of a standard.

It fails in smaller, more ordinary ways. A client is allowed to send a direct front-channel authorization request even though the request should have gone through PAR. A public client is treated like a confidential one. PKCE is present, but not with S256. Tokens remain bearer tokens because sender constraints were documented as a recommendation rather than enforced as policy.

That is the useful way to read the FAPI 2.0 Security Profile: not as a label, and not as one more protocol feature, but as a profile that removes unsafe degrees of freedom for high-value API access.

In the ProAuth v3 preview, FAPI 2.0 support follows that idea. The profile is opt-in for tenants and client applications, disabled by default, and intended for flows where authorization requests, client authentication, and access tokens need stronger runtime guarantees.

This is preview content for the upcoming ProAuth v3 release. It describes the guardrails available in the preview; it does not claim OpenID certification.

Profiles matter because configuration drifts

Most teams already know the individual controls in the FAPI stack: authorization code flow, PAR, PKCE, strong client authentication, DPoP or mTLS sender constraints, and short-lived protocol handles. The harder part is making those decisions stay true after the first integration workshop.

FAPI helps because it changes the failure mode. Instead of relying on a checklist that says “please configure this client securely”, the authorization server rejects the request when the active policy is not satisfied.

flowchart TD
    Start["High-value client request"] --> Policy{"FAPI policy active?"}
    Policy -->|"No"| Standard["Normal OIDC/OAuth policy"]
    Policy -->|"Yes"| Authz["Authorization request guardrails"]

    subgraph AuthzStack["Authorization request"]
        Authz --> Par["Require PAR"]
        Par --> Code["Require response_type=code"]
        Code --> Pkce["Require S256 PKCE"]
        Pkce --> Front["Browser request carries only client_id and request_uri"]
    end

    Front --> Token["Token request guardrails"]

    subgraph TokenStack["Token request"]
        Token --> Confidential["Require confidential client"]
        Confidential --> ClientAuth["Require private_key_jwt or mTLS client authentication"]
        ClientAuth --> Sender["Require DPoP-bound or certificate-bound tokens"]
    end

    Sender --> Issue["Issue tokens only after the guardrails pass"]
    Authz -. "Missing PAR, wrong response type, or weak PKCE" .-> RejectAuthz["Reject before user interaction continues"]
    ClientAuth -. "Weak client auth or bearer-only issuance" .-> RejectToken["Reject before tokens are issued"]

The diagram is intentionally a stack, not a badge. A client does not become high assurance because a setting exists somewhere. It becomes easier to operate safely when each layer rejects the insecure alternative.

How ProAuth resolves the effective policy

The v3 preview keeps the rollout model deliberately conservative. FAPI mode is disabled by default, and existing tenants and client applications continue to use non-FAPI behavior unless the profile is explicitly enabled.

ProAuth resolves the effective policy in this order:

LevelSettingEffect
Tenant forceRequireFapi2SecurityProfile = trueForces FAPI 2.0 for every client in the tenant
Client applicationSecurityProfileLets an individual client choose its security profile
Tenant defaultDefaultClientSecurityProfileApplies when the client application’s SecurityProfile is null

The profile values are None for normal OIDC/OAuth behavior and Fapi2Security for FAPI 2.0 enforcement. Migrated existing clients are set to explicit None, which preserves compatibility instead of silently moving old integrations into a stricter policy.

That matters operationally. A tenant can start with one high-value client, learn what breaks, update client libraries and resource-server checks, and only later decide whether tenant-wide enforcement is appropriate.

What a FAPI client must satisfy

For a ProAuth v3 preview client running with Fapi2Security, the required shape is narrow by design:

AreaRequired behavior
Authorization flowAuthorization code flow only, with response_type=code
Authorization requestPAR for every authorization request
Browser-facing requestOnly client_id and request_uri after PAR
PKCEcode_challenge_method=S256
Client typeConfidential client application
Client authenticationprivate_key_jwt, tls_client_auth, or self_signed_tls_client_auth
Access token protectionSender-constrained access tokens through DPoP or certificate binding

The token endpoint is similarly constrained for FAPI clients: authorization-code redemption requires the original code to have been issued with S256 PKCE, and the token request must include the matching code_verifier. Refresh-token use still requires the configured FAPI client authentication and sender constraint.

ProAuth also caps the relevant lifetimes in FAPI mode: PAR handles stay below 600 seconds, and authorization codes are limited to at most 60 seconds.

The useful rejection happens early

The best security control is often the boring one that fails early.

In FAPI mode, a direct authorization request without PAR is not an alternative path. A request with implicit or hybrid response types is not an acceptable variation. A missing or non-S256 PKCE challenge is not “close enough”. A token request with weak client authentication, missing sender-constraint configuration, or a missing DPoP proof for a DPoP-bound client fails before token issuance.

Those errors use the normal OAuth/OIDC error model, such as invalid_request, unsupported_response_type, invalid_client, or unauthorized_client. The important point is not that the error names are exotic. They are not. The point is that configuration drift becomes a protocol failure at runtime.

Discovery also follows the same idea. ProAuth discovery stays broad unless the tenant requires FAPI. When RequireFapi2SecurityProfile is enabled at tenant level, discovery advertises only FAPI-compatible response types, grant types, and client authentication methods, and includes require_pushed_authorization_requests.

Sender-constrained refresh tokens change the tradeoff

One subtle difference is refresh-token handling.

For non-FAPI clients, ProAuth uses rolling refresh-token rotation: each successful refresh returns a new refresh token, revokes the used token, and treats reuse of an older token as a replay signal for that user and client.

FAPI clients use stable sender-constrained refresh tokens. A successful refresh returns new access and ID tokens but does not return a replacement refresh_token. The client keeps using the original refresh token until it expires or is revoked, and every refresh request still has to satisfy the configured client authentication and sender constraint. For DPoP-bound clients, that means a valid DPoP proof. For mTLS clients, it means the configured client certificate.

That is not weaker rotation by another name. It is a different replay-control model built around proof-bound use of the refresh token.

Roll out the profile where the risk justifies it

FAPI should not become the new checkbox everyone enables because it sounds serious.

It belongs first on clients where the risk, partner requirements, or regulatory context justify the stricter behavior: payment and delegated-action flows, sensitive APIs, cross-organization integrations, and environments where a configuration mistake should fail closed rather than become a production incident.

It also does not replace application authorization, API-side token and proof validation, key and certificate operations, monitoring, or governance between ecosystem participants. The profile makes the OAuth/OIDC layer stricter. The rest of the system still has to be designed and operated with the same care.

A pragmatic rollout usually looks like this:

  1. Pick one high-value client application.
  2. Move authorization requests to PAR.
  3. Confirm code flow with S256 PKCE.
  4. Move client authentication to private_key_jwt or mTLS.
  5. Enable DPoP-bound or certificate-bound access tokens.
  6. Set the client SecurityProfile to Fapi2Security.
  7. Consider tenant defaults or RequireFapi2SecurityProfile only after upgraded clients and APIs are ready.

The order matters less than the principle: make the profile enforce what the client and resource servers can actually operate.

For implementation details, start with the ProAuth FAPI 2.0 Security Profile documentation. The related ProAuth docs for PAR and JAR, DPoP token binding, and mutual TLS client authentication explain the underlying building blocks. For the standards background, read the FAPI 2.0 Security Profile, RFC 9126, RFC 9449, RFC 8705, and the OAuth 2.0 Security Best Current Practice. The broader ProAuth v3 security expansion is tracked in the ProAuth 3.0.0 release notes.