Security profiles
Standard vs the FAPI 2.0/CIBA security profile — where the profile is set, what discovery advertises, the registration rules a FAPI application must satisfy and their 422 codes, and the runtime rules that follow.
Every SenseCrypt tenant runs one of two security profiles:
- Standard — the general-purpose OIDC/OAuth 2.0 issuer described throughout these docs. Authorization Code with PKCE, client secrets or asymmetric client authentication, bearer tokens.
- FAPI 2.0/CIBA (
plain_fapi) — the hardened profile: pushed authorization requests only, asymmetric client authentication only, sender-constrained tokens, noRS256signatures, short-lived authorization codes, and — for CIBA — signed backchannel requests.
plain_fapi is the only profile value this issuer implements. It appears in two places, and the two mean different things.
Where the profile is set
| Level | Field | Set where | Mutable? |
|---|---|---|---|
| Tenant | fapi_profile on the tenant | POST /v1/admin/tenants | No — fixed at creation, like the slug. A PATCH that names it is refused 422 with code: "immutable_field". |
| Application | fapi_profile on the OIDC application | POST /v1/admin/oidc-apps, or PATCH later | Yes, subject to the rules below |
A FAPI 2.0/CIBA tenant holds every application to the profile. A new application in such a tenant is created with the tenant's profile whether or not the request asks for it, and a request that tries to set another value — including an explicit null — is refused 422 with code: "fapi.inherited_from_tenant". The same rule applies on PATCH, so there is no per-application opt-out.
A Standard tenant's applications adopt the profile one at a time. Set fapi_profile: "plain_fapi" on the application you want hardened; other applications in the tenant are unaffected.
A FAPI application's own rules hold whatever the tenant's discovery document says. The document describes what the issuer accepts from every client; the profile on your application is what your client is held to. Creating an unrelated Standard application in the tenant never relaxes a FAPI application's requirements.
What discovery advertises
The shape of the discovery document is derived, not a separate switch:
- A FAPI 2.0/CIBA tenant advertises the FAPI shape always — every application in it is held to the profile by construction.
- A Standard tenant advertises the FAPI shape while it has at least one live OIDC application and every one of them adopts the profile. The next Standard application takes the shape away again. A tenant with no live OIDC applications advertises the standard shape.
The difference on the wire:
| Metadata | Standard | FAPI 2.0/CIBA |
|---|---|---|
require_pushed_authorization_requests | false | true |
response_types_supported | code plus the implicit and hybrid types | ["code"] |
response_modes_supported | query, fragment, form_post, their .jwt twins and the generic jwt | ["query", "jwt", "query.jwt"] |
grant_types_supported | includes implicit | no implicit |
token_endpoint_auth_methods_supported | client_secret_post, client_secret_basic, client_secret_jwt, private_key_jwt | private_key_jwt — plus the two mutual TLS methods on either shape, where they are available |
id_token_signing_alg_values_supported | ES256, RS256 | ["ES256"] |
authorization_signing_alg_values_supported | ES256, RS256 | ["ES256"] |
token_endpoint_auth_signing_alg_values_supported | HS256, RS256, ES256, PS256 | ES256, PS256 |
request_object_signing_alg_values_supported | ES256, PS256, RS256 | ES256, PS256 |
backchannel_authentication_request_signing_alg_values_supported | ES256, PS256, RS256 | ES256, PS256 |
backchannel_token_delivery_modes_supported | poll, ping, push | poll, ping |
Losing or gaining the earned shape is recorded in the audit log as issuer.advertised_profile_changed, with the application that caused it.
Management API service accounts (/v1/admin/m2m-apps) authenticate with a client secret at the same token endpoint and stay outside the profile by design: such a credential can only ever mint for the tenant's own Management API audience, never for a resource a relying party integrates against. They are not counted when the shape is derived.
Registration rules
Setting fapi_profile on an application is refused unless the row that would result satisfies every rule below. Each refusal is a 422 in the management envelope with a stable code in details[] — see Error codes. The rules are identical on create and on PATCH: the merged state is what is checked.
code | The rule |
|---|---|
fapi.requires_confidential_client | client_type must be confidential. A SPA (public) client cannot adopt the profile. |
fapi.requires_pkce | require_pkce must be true. |
fapi.requires_key_or_certificate_auth | token_endpoint_auth_method must be private_key_jwt, tls_client_auth or self_signed_tls_client_auth. Shared-secret methods are refused. |
fapi.requires_sender_constrained_tokens | At least one of dpop_bound_access_tokens / tls_client_certificate_bound_access_tokens must be on. See Sender-constrained tokens. |
fapi.forbids_rs256 | id_token_signed_response_alg must not be RS256. ES256 and RS256 are the two registrable values, so a FAPI application signs its id_tokens with ES256. |
fapi.requires_code_only | response_types must be exactly ["code"] — no implicit or hybrid type. |
fapi.requires_https_redirect_uris | Every entry in allowed_redirect_uris must be https, a native-app custom scheme, or a loopback http address (localhost, 127.0.0.1, ::1). |
fapi.forbids_ciba_push | backchannel_token_delivery_mode must not be push — FAPI-CIBA is poll or ping. |
fapi.inherited_from_tenant | The tenant is a FAPI 2.0/CIBA tenant and the request tried to set another profile (or clear it) on one of its applications. |
fapi_ciba.requires_signing_keys | The application has a CIBA delivery mode, so it must have registered request-signing keys (jwks or jwks_uri) — every one of its backchannel requests is signed and has to be verifiable. |
fapi_ciba.requires_certificate_binding | The application has a CIBA delivery mode, so tls_client_certificate_bound_access_tokens must be on. |
Runtime rules
Once the profile is on the application, these apply to its requests whatever the discovery document advertises:
- Pushed authorization requests only. A direct
/authorizefrom a FAPI application is refusedinvalid_request(as an error redirect when the plainredirect_uriparameter is registered, otherwise on SenseCrypt's refusal page). Push to thepushed_authorization_request_endpointand redirect with the returnedrequest_uri. - Authorization codes live at most 60 seconds, whatever the deployment's default code lifetime is.
- No
RS256anywhere. The id_token (and a signed authorization response, which uses the same signer — see JARM) isES256; a signed request object and aprivate_key_jwtclient assertion must beES256orPS256. AnRS256client assertion or request object from a FAPI application is refused. - A client assertion's
audmust be a string, and at most endpoints it must be the issuer identifier. An arrayaudis refused even when it contains the issuer. At the token endpoint (on theauthorization_codeandclient_credentialsgrants) and at/par, only the issuer identifier is accepted; a non-FAPI client may name either the issuer identifier or the token endpoint URL at both of those endpoints — there is no PAR-URL audience for any client. Two relaxations: the CIBA backchannel endpoint accepts its own URL and the token endpoint URL from every client (CIBA's own rule), and a FAPI application registered for CIBA may name the token endpoint URL at the token endpoint on the CIBA andrefresh_tokengrants (the FAPI-CIBA lineage). - A DPoP proof must carry a server nonce. A FAPI application's proof without one is answered
use_dpop_noncewith a freshDPoP-Nonceheader, at the token endpoint and at the UserInfo endpoint. Retry with the value from the header. - Signed backchannel requests for CIBA. See FAPI 2.0/CIBA clients.
Availability of the mutual TLS half
tls_client_auth, self_signed_tls_client_auth and tls_client_certificate_bound_access_tokens need a deployment that terminates client certificates on a dedicated mutual TLS listener — the deployments where discovery publishes mtls_endpoint_aliases (and tls_client_certificate_bound_access_tokens: true). Check the tenant's discovery document before you plan around them.
Where that listener is not configured, registering either mutual TLS authentication method or certificate-bound tokens is refused 422 with code: "mtls.listener_unavailable" — a client that could never authenticate is refused up front rather than at its first token request. A FAPI-CIBA application is affected by this directly: fapi_ciba.requires_certificate_binding makes certificate binding mandatory, so a FAPI application with a CIBA delivery mode can only be registered where the aliases are published.
DPoP has no such dependency: it is available on every deployment and to every application, FAPI or not.
Related
- Sender-constrained tokens — DPoP and mutual TLS,
cnf, and the resource-server checks. - Signed requests and responses — JAR at PAR, and JARM.
- CIBA — the decoupled flow, including the FAPI-CIBA rules.
- OIDC & OAuth 2.0 — discovery, PAR, client authentication.
- Managing tenants — creating a tenant and reading its issuer settings.
Authorization
How SenseCrypt decides who may sign in (the default-closed group-to-app access gate) and what end users may do downstream (resource servers, permissions, and roles that shape the permissions claim).
Integrations
Task-oriented guides for connecting your application to SenseCrypt — OIDC login, SAML SSO, SCIM provisioning, machine-to-machine, and CIBA.