Specification: FAPI 2.0 Security Profile
FAPI 2.0 narrows OAuth 2.0 for high-risk APIs: confidential clients, pushed authorization requests, PKCE and sender-constrained access tokens. In Versola these rules apply to every client of a tenant whose security profile is fapi2, which is the default for new tenants.
Status
Partially implemented. Checked claims: 31 of 35.
Versola is not FAPI 2.0 certified yet, and no conformance certification is claimed. We plan to go through the certification. Versola has not been through the OpenID Foundation FAPI 2.0 conformance suite, and no conformance-suite results are published. Each line below is backed by code and by an end-to-end test in the product repository; the test names are given in parentheses.
Client registration
central checks these when a client is created or patched and lists every violation in one error.
- Confidential clients only — a public client (
authMethod: none) is refused at registration, and a public client that already exists is refused withinvalid_clientat/token,/par,/introspectand/revoke(§5.3.2.1;SecurityProfileApiSpec,SecurityProfileRuntimeSpec) - Client authentication by
private_key_jwt,tls_client_authorself_signed_tls_client_authonly — aclient_secretclient is refused (§5.3.2.1;SecurityProfileApiSpec) - Sender-constrained access tokens required — the client sets
dpopBoundAccessTokens, authenticates with a certificate, or setscertificateBoundAccessTokens(§5.3.2.2;SecurityProfileApiSpec) - Pushed authorization requests required for every client that has a redirect URI —
requirePushedAuthorizationRequests(§5.3.2.2;SecurityProfileApiSpec) -
httpsredirect URIs only; loopbackhttpis allowed for native clients, and for any client outsideprod; private-use schemes are refused (§5.3.2.2;SecurityProfileApiSpec) - A patch that would take a client out of the profile is refused (
SecurityProfileApiSpec) - The profile is fixed when the tenant is created and cannot be switched later (
SecurityProfileApiSpec)
Authorization request
- Only the authorization code flow is offered for plain OAuth — implicit and resource owner password grants are not implemented
- Pushed authorization requests —
POST /parreturns a single-userequest_uribound to the pushing client (see RFC 9126;PushedAuthorizationSpec) - PKCE with
S256required for every client, including confidential ones;plainis not accepted (see RFC 7636;PushedAuthorizationSpec) - Redirect URIs matched by exact string comparison
- The authorization response carries
iss(see RFC 9207) - Signed request objects (JAR) are accepted:
nbfrequired and at most 60 minutes old,expat most 60 minutes afternbf,jtirequired and replay-checked,audexactly the issuer string (see RFC 9101;JarFlowSpec) - JARM signed authorization responses are available as an option (see JARM;
JarmFlowSpec) -
response_type=code id_tokenrefused forfapi2clients — not enforced; the hybrid flow stays available on afapi2tenant (withnoncerequired)
Client authentication
-
private_key_jwt:audmust be exactly the issuer, sent as a single string; an array or an endpoint URL is refused (§5.3.2.1-8;PrivateKeyJwtSpec) -
iatandnbfup to 60 s in the future are accepted, further ahead is refused (§5.3.2.1-13;PrivateKeyJwtSpec) - Assertion lifetime bounded by the tenant’s client-assertion maximum (30–900 s, default 300 s), and every
jtiis single use (PrivateKeyJwtSpec) - A client that registered keys cannot authenticate with a secret, and two methods in one request are refused (
PrivateKeyJwtSpec) -
tls_client_authandself_signed_tls_client_authat/token,/par,/introspectand/revoke, through the mutual TLS listener or a proxy-forwarded certificate (see RFC 8705;MutualTlsListenerSpec,MutualTlsSpec)
Token endpoint
- Authorization codes are single use; presenting a used code revokes the tokens issued from it
-
Cache-Control: no-storeandPragma: no-cacheon token responses - Refresh tokens are bound to the sender key or certificate of the grant (see RFC 9449 and RFC 8705;
MutualTlsSpec)
Sender-constrained tokens
- DPoP proofs validated for
typ, embeddedjwk,htm,htu,iat,jtiandath, withcnf.jktin the token (see RFC 9449) - DPoP replay protection shared across replicas
- DPoP key policy: RSA keys under 2048 bits are refused and a client can narrow the accepted algorithms (
DpopKeyPolicySpec) - Server-provided DPoP nonce —
use_dpop_nonceandDPoP-Nonce, enabled per tenant withrequireDpopNonce(off by default) - Certificate-bound tokens:
cnf.x5t#S256in the token and in introspection, enforced at/userinfo(see RFC 8705;MutualTlsSpec) - A key-bound token presented as
Beareris refused at/userinfoand atedge(DpopBoundTokenSpec)
Algorithms
- A
fapi2tenant cannot select an RS256 signing key; PS256 and ES256 keys are accepted (SigningKeySelectionSpec) - DPoP proofs, client assertions and request objects default to
ES256andPS256;RS256is not in the default set - RS256 excluded from DPoP, client-assertion and request-object algorithm lists for
fapi2tenants — not enforced per tenant; these lists are deployment-wide metadata, and an operator who addsRS256to them admits it for every tenant -
EdDSA— not supported
Resource server
-
edgeverifies the DPoP proof,ath, the key binding and replay on every proxied call, and refuses a token bound to both key and certificate on every presentation (see Edge;DpopBoundTokenSpec)
Transport
- TLS version and cipher-suite restrictions — not enforced by Versola; TLS terminates at the reverse proxy or ingress that the operator configures
Notes
- The
standardprofile is not FAPI-shaped. It admits client secrets, public clients, RS256 signing keys and, for confidential clients, requests without PKCE. - Apps fronted by
edgekeep the FAPI shape on either profile: certificate-bound tokens for web clients, DPoP-bound tokens and App Link redirects for native ones. - The FAPI 2.0 Message Signing parts (JAR, JARM) are optional features here and are not forced by the profile.
Where to look
Security profiles explains the fapi2 and standard profiles, and Sender-constrained tokens explains DPoP and certificate binding.