The connection that moves your money is a control.
Not long ago, moving money meant a person logging into a bank portal and uploading a file. Today it means software talking to software. An ERP calls a bank to fetch statements and send payments. A treasury system pulls balances across a dozen accounts. A payments provider posts settlement data back into the ledger. Each of those connections is an API, and each one holds real authority: to read sensitive records, and often to move funds. The security of that connection is a finance control, as much as any approval limit or reconciliation, even though it usually lives in a place finance never looks.
The reassuring development is that the bar for those connections is no longer a matter of taste. On 22 February 2025 the OpenID Foundation published the FAPI 2.0 Security Profile as a final specification, and in July 2025 it announced conformance tests and certification for it. FAPI began life as the Financial grade API work and is now a general profile of OAuth 2.0 for high security applications. It says, in normative terms, what a connection that carries this kind of authority should look like, and its security is formally analysed against a defined attacker model rather than argued by assertion. For a finance team, that means there is a named, testable target to hold an integration to.
The heart of it is one idea worth stating plainly. In the old model, an access token is a bearer credential: whoever holds it can use it, exactly like a password written on a card. That works until a copy of the token leaks, from a log file, a proxy, an error report, or a breached server, and then it works just as well for the person who found it. FAPI 2.0 requires tokens to be sender constrained, which means each token is bound to the specific client it was issued to. A copied token presented by anyone else is refused, because the holder cannot prove possession of the certificate or key the token is tied to. A leak stops being a breach.
Read as a systems problem, this is not a research project, it is a register a finance team can own. The rest of this piece walks through what FAPI 2.0 actually requires, why a sender constrained token changes the economics of a stolen credential, and the connection record worth building so every integration can show, on demand, the profile it meets and the gaps it still has.
What FAPI 2.0 requires, and what each rule stops.
The profile is a set of requirements on the authorization server, the client, and the resource server, and each one closes a specific attack. It is worth reading them not as protocol trivia but as controls, because that is what they are. The table below sets out the core requirements, what FAPI 2.0 asks, the attack each one removes, and what it needs from a connection a finance team owns.
| The requirement | What FAPI 2.0 asks | What it stops | What it needs from your connection |
|---|---|---|---|
| Sender constrained access tokens | The server issues only tokens bound to the client, by mutual TLS or DPoP, and resource servers verify the binding before honouring a token | A token copied from a log, a proxy, or a breached server being replayed by anyone else, because the thief cannot prove possession of the certificate or key | Each connection presents a client certificate or a DPoP key, and the token is checked against it, so possession of the token alone is not enough to use it. |
| Pushed authorization requests | The client registers the authorization request with the server first, over an authenticated back channel, and the server rejects any request that did not arrive that way | Tampering with or leaking the request parameters in the browser front channel, because the request the user approves is the exact one the client already lodged | The integration uses pushed authorization requests, so what is authorized is fixed server side before the user step and cannot be rewritten in transit. |
| PKCE with S256 | The client proves it started the flow, presenting a code verifier that matches the S256 challenge it sent, before any authorization code is exchanged for a token | An intercepted authorization code being redeemed by an attacker, because the code is useless without the matching verifier only the real client holds | PKCE with S256 on every authorization code flow, so a code captured in transit cannot be turned into a token by whoever grabbed it. |
| The iss check and code only responses | The authorization response carries an iss parameter the client checks, and the only response type is code, so tokens never travel through the front channel | Mix up attacks across multiple providers, and token leakage through the browser, because the client confirms which server answered and no token rides in the URL | The client checks the iss parameter and relies on the authorization code flow alone, so a response cannot be redirected from one provider to confuse another. |
| Tokens in the header, never the URL | Access tokens travel in the HTTP header and are not accepted in query parameters, so a token is not written into places that keep a copy | A live token ending up in server logs, browser history, and referer headers, where it sits readable long after the request that carried it | Transport that keeps the token out of the URL and bound to the sender, so a token is neither logged by accident nor usable if it is. |
The thread running through all of it is that possession should not equal authority. A bearer token, an authorization code in transit, a request travelling through the browser, a token sitting in a URL: each is a thing that grants power to whoever holds a copy. FAPI 2.0 attaches a proof to each of them, a key the client must present, a verifier only it knows, a request lodged in advance, so that holding the artefact is no longer enough. That is why the profile is stronger than any single long secret. It does not ask you to keep more things private, it removes the value of the things that leak.
It is also worth being clear about what this is and is not. FAPI 2.0 is a security profile, so it governs how a connection is secured, not who is allowed to have one. That second question, consent and scope, matters too, and it is the subject of a companion discipline: recording who authorized a connection and what it may do. The two belong side by side. A connection can be perfectly consented and still hand out replayable bearer tokens, and a connection can be beautifully secured and still hold authority nobody signed off. The register worth building carries both, which is where the next section goes.
The connection register, read as a governed control.
A worked example makes it concrete. Take one connection, an ERP integration to the group operating bank that can fetch statements and initiate payments. It authenticates with a client certificate, its access tokens are bound to that certificate by mutual TLS, it uses pushed authorization requests and PKCE, it checks the iss parameter, and it never puts a token in a URL. The certificate has a known expiry and the key rotates on a schedule. Recorded that way, this connection can answer for itself: a reviewer can see the profile it meets without opening the code, and a token stolen from its logs would not be honoured anywhere.
Here is that logic expressed as a record. The connection carries its identity, purpose, and owner. A security profile block records how tokens are bound, how the client authenticates, and whether the front channel protections are on. A credentials block tracks expiry and rotation. A consent block records the authority the connection holds. And an exception query at the end names the gaps that would make any of it untrustworthy, starting with a token that is not sender constrained.
An API connection record shaped for a financial-grade security profile
# API connection register shaped for a financial-grade security profile
# Names, ids, and dates are placeholders for shape, not real values.
connection:
connection_id: "CX-2043"
counterparty: "Group operating bank, statements and payments"
purpose: "Fetch statements, initiate payments"
owner: "Treasury systems lead"
environment: production
security_profile:
target: "FAPI 2.0 Security Profile"
sender_constrained: true
binding_method: mtls # mtls (RFC 8705) or dpop (RFC 9449)
client_authentication: mtls # mtls or private_key_jwt
pushed_authorization_requests: true # PAR, RFC 9126
pkce: S256 # RFC 7636
iss_checked: true # RFC 9207, mix-up defence
token_in_url: false
response_type: code
credentials:
client_cert_expires: 2027-03-01
key_rotation: "every 90 days, last rotated 2026-08-15"
secret_storage: "hardware or KMS, never in source or config"
consent_and_scope:
scope: "accounts:read payments:initiate"
consent_reference: "CN-88231"
granted_by: "Group treasurer"
exception_query: # the readiness control, run on a schedule
- connection issuing or accepting tokens that are not sender constrained
- access token seen in a query string or a server log
- authorization request accepted without pushed authorization requests
- authorization-code flow without PKCE S256
- client credential or certificate past its rotation window
- connection with no named owner or no recorded consentThe exception query is what keeps it honest. A connection that still issues plain bearer tokens is the one a leak turns into a loss. A token found in a query string is already halfway into a log. A flow without pushed authorization requests or PKCE is an open door that has nothing to do with how long your secrets are. A certificate past its rotation window is a quiet clock running down. None of this is exotic. It is ordinary estate hygiene, measured against one named standard, so a weak connection is a query result you can fix on a Tuesday rather than an incident you explain on a Friday night.
An implementation checklist.
- 1.Inventory every connection that can read money data or move funds. You cannot govern what you have not listed, so start with a register of each API integration to a bank, a payment provider, or a fintech, what it can do, and who owns it. A connection nobody can name is the one that keeps a shared credential alive years after the person who set it up has gone.
- 2.Set FAPI 2.0 as the target for connections that matter, and record the gap. For each connection, note the security profile it actually meets today and the one you want it to meet. Where a bank or provider already offers a FAPI 2.0 endpoint, adopt it. Where it does not, the register still shows the gap honestly, which is what turns a vague worry into a tracked piece of work.
- 3.Make tokens sender constrained, by mutual TLS or DPoP. This is the single change that matters most, because it removes the value of a stolen token. Bind the token to a client certificate or a DPoP key, verify the binding at the resource server, and a copied token becomes useless to anyone who cannot present the matching key. Pick one mechanism per connection and record which.
- 4.Use pushed authorization requests and PKCE on every authorization flow. Register the request with the server first so its parameters cannot be tampered with in the browser, and require PKCE with S256 so an intercepted code cannot be redeemed. Both are cheap to switch on where the provider supports them, and both close attacks that have nothing to do with how strong your secrets are.
- 5.Keep tokens out of URLs and check the iss parameter. Send access tokens in the header, never in a query string where they land in logs and history, and have the client confirm which server answered so a response cannot be redirected to confuse another provider. These are small settings that quietly prevent a token leaking or a mix up between two integrations.
- 6.Authenticate the client properly and store secrets like they matter. Use mutual TLS or a private key JWT for client authentication rather than a shared secret pasted into config, hold keys in hardware or a key management service, and rotate certificates and keys on a schedule the register tracks. A credential that never rotates and lives in a config file is the soft spot a strong protocol cannot save.
- 7.Run one exception query before you trust the estate. Any connection that is not sender constrained, any token seen in a query string or a log, any flow without pushed authorization requests or PKCE, any credential past its rotation window, and any connection with no owner or no recorded consent is a gap. While that list has entries, the estate is weaker than the strongest connection in it, and the list is far cheaper to clear than an incident to explain.
Failure modes, framed so you can avoid them.
- Treating a bearer token as good enough because it works. A plain bearer token is a password on a string: whoever holds it can use it. It works perfectly right up to the moment a copy leaks from a log or a proxy, and then it works perfectly for the attacker too. Sender constraining is the fix precisely because it makes possession of the token insufficient, so a leak stops being a breach.
- Putting an access token in a URL. A token in a query string is written into web server logs, load balancer logs, browser history, and the referer header of the next request, any of which can outlive the token and be read by someone who should never see it. Keeping the token in the header is a one line habit that removes a whole class of quiet exposure.
- Leaving the request open to front channel tampering. Without pushed authorization requests, the parameters of an authorization request travel through the browser, where they can be altered or observed. The user then approves something subtly different from what the client intended. Lodging the request server side first means the thing approved is the thing sent, with nothing to rewrite in between.
- Relying on the strength of a shared secret. A long shared secret feels safe until it is pasted into a config file, checked into a repository, or copied into a runbook, after which its length stops mattering. Client authentication by mutual TLS or a private key held in a key store does not have a value to paste, which is why it survives the human handling that secrets do not.
- Never rotating certificates and keys. A binding is only as good as the key behind it, and a key that is never rotated is a key that eventually leaks without anyone noticing. Rotation on a schedule, tracked in the register, means a compromised key has a short life, and a certificate quietly expiring becomes a planned change rather than a Monday morning outage.
- Skipping the iss check with more than one provider. When a system talks to several authorization servers, an attacker can try to make a response from one look like it came from another. Checking the iss parameter is the small step that keeps the responses straight, and leaving it out is the kind of gap that only shows up once someone goes looking for it.
- Buying a certified server and calling the estate secure. A FAPI 2.0 certified endpoint at the bank protects that endpoint. It does nothing for the connection you built to a second provider with a shared secret and a token in the URL. Security is a property of each connection, so the register has to cover all of them, not just the one that came with a certificate.
- Confusing consent with connection security. Recording who consented to a connection is necessary, and it is not the same as securing how the tokens for that connection are issued and carried. A perfectly consented connection that hands out replayable bearer tokens is still a soft target, so the register needs both the consent and the security profile, not one standing in for the other.
What this asks of the data model.
- The connection is a first class record, not a line in a runbook. It carries an identifier, the counterparty, what it can do, the environment, and a named owner, so the estate is a queryable register rather than tribal knowledge held by whoever built each integration.
- The security profile is captured as fields, not assumed. Whether tokens are sender constrained, the binding mechanism, the client authentication method, whether pushed authorization requests and PKCE are used, and whether tokens ever appear in a URL are recorded values, so a connection below the bar is a query result rather than a discovery during an incident.
- Sender constraining names its mechanism. Mutual TLS and DPoP both bind a token to the client, and the record says which one a connection uses, so a resource server and an auditor can both see that the token is not a free floating bearer credential.
- Client authentication is a stored choice with a basis. Mutual TLS or a private key JWT is recorded per connection, rather than a shared secret assumed to exist somewhere, so the way a client proves who it is can be reviewed and rotated like any other control.
- Credentials carry expiry and rotation. Certificate expiry, key rotation cadence, and where secrets live are fields, so a credential drifting past its rotation window or a certificate about to expire is visible ahead of time rather than at the moment a connection fails.
- Consent and scope sit beside the security profile, not instead of it. The scope a connection was granted, who granted it, and the consent reference are recorded next to how the connection is secured, so a review sees both the authority a connection holds and the strength with which it holds it.
- The estate is graded against one target. FAPI 2.0 is the reference the register measures against, so every connection has a clear pass or gap rather than a private opinion, and the gaps become a backlog with owners rather than a feeling that something is not quite right.
- Completeness reduces to one exception query. Any connection not sender constrained, any token in a query string or a log, any flow without pushed authorization requests or PKCE, any credential past rotation, and any connection with no owner or consent is the exception list, and it can run on a schedule rather than surfacing after a token leaks.
The evidence to keep.
- The connection register, listing every API integration that can read money data or move funds, its counterparty, its owner, and what it is permitted to do, so the estate is documented rather than remembered.
- The security profile per connection, showing whether tokens are sender constrained and by which mechanism, whether pushed authorization requests and PKCE are used, the client authentication method, and whether tokens ever travel in a URL, so effectiveness is a record and not a claim.
- The sender constraining evidence, showing that access tokens are bound to a client certificate or a DPoP key and that resource servers verify the binding, so a reviewer can see a stolen token would not be honoured.
- The credential lifecycle trail, showing certificate expiry, key rotation dates, and secret storage, so a credential past its rotation window or a certificate near expiry is evidenced rather than assumed to be current.
- The consent and scope record per connection, showing who authorized it and what it may do, tied to the same connection whose security profile is recorded, so authority and security are evidenced together.
- The conformance or certification evidence where a provider offers it, showing which endpoints have been tested against the FAPI 2.0 profile, so reliance on a certified endpoint is documented rather than assumed for the whole estate.
- The change history for each connection, showing when its profile was upgraded, when a credential was rotated, and who approved the change, so the register is a living control rather than a snapshot taken once.
- The exception query output, ideally empty, listing any connection not sender constrained, any token seen in a query string or a log, any flow without pushed authorization requests or PKCE, and any credential past rotation, so the gaps were checked before the estate was relied on.
Questions worth asking in your own review.
- Could we list today every API connection that can read our balances or move our money, with an owner for each, or would some surface only when they broke?
- For each of those connections, are the access tokens sender constrained by mutual TLS or DPoP, so a copied token would be useless to whoever holds it?
- Do our authorization flows use pushed authorization requests and PKCE, or can the request be tampered with in the browser and a code be redeemed by an interceptor?
- Are we sure no access token is ever written into a URL, where it would land in logs, history, and referer headers?
- How do our clients authenticate, and are we relying on a shared secret that has been pasted into a config file or a repository somewhere?
- When do our certificates and keys rotate, and would we know before one expired or after a connection went down?
- If we talk to more than one provider, do our clients check the iss parameter so a response from one cannot be passed off as another?
- Have we recorded both the consent a connection holds and the security profile it meets, or are we treating one as if it covered the other?
What this adds up to.
The money in a modern finance function moves along API connections, and each of those connections is a control whether or not anyone treats it as one. The good news is that the standard for securing them is now finished and testable. FAPI 2.0 takes the artefacts that used to grant power to whoever held a copy, the token, the code, the request, and ties a proof to each, so that possession alone stops being enough. The headline effect is that a stolen token becomes worthless to the thief who took it.
The build that makes it real is a register and a few deliberate choices. List every connection that can read money data or move funds, set FAPI 2.0 as the target and record the gap, make tokens sender constrained by mutual TLS or DPoP, switch on pushed authorization requests and PKCE, keep tokens out of URLs, authenticate clients without a shared secret, and rotate credentials on a schedule the register tracks. Hold the consent beside the security profile so authority and strength are visible together.
If there is one place to start, run the exception query against your own estate before you trust it: every connection that is not sender constrained, every token seen in a query string or a log, every flow without pushed authorization requests or PKCE, and every credential past its rotation window. For a tidy estate that list is short and the work is scheduled. For a sprawling one it is the work, and it is far better found now, at the desk, than after a token has leaked from a place you forgot it could.
Sources.
- OpenID Foundation, FAPI 2.0 Security Profile, Final, published 22 February 2025, read directly from the specification. Source for the scope, an API security profile suitable for high security applications based on OAuth 2.0; and for the normative requirements that an authorization server shall only issue sender constrained access tokens using mutual TLS (RFC 8705) or DPoP (RFC 9449) and shall authenticate clients using mutual TLS or private_key_jwt (section 5.3.2.1); that it shall require the response_type to be code, shall support client authenticated pushed authorization requests according to RFC 9126 and reject authorization requests sent without them, shall require PKCE (RFC 7636) with the S256 code challenge method, and shall return an iss parameter in the authorization response according to RFC 9207 (section 5.3.2.2); that clients shall support sender constrained access tokens, shall send access tokens in the HTTP header rather than in query parameters, shall use pushed authorization requests, and shall check the iss parameter to prevent mix up attacks (sections 5.3.3.1 and 5.3.3.2); and that resource servers shall not accept access tokens in query parameters and shall support and verify sender constrained access tokens (section 5.3.4). Source too for the statement that the profile aims to reach the goals of the FAPI 2.0 Attacker Model and that its security property is formally analysed under that model.
- OpenID Foundation, Final Tests for FAPI 2.0 Security Profile and Message Signing, announcement published 9 July 2025. Source for the FAPI 2.0 Security Profile being approved as a final specification, for the companion FAPI 2.0 Message Signing profile being finalised in 2025, for the availability of conformance tests and certification for authorization servers and OAuth clients, and for the description that the FAPI 2.0 specifications are simpler, more secure and more interoperable than the earlier generation and have undergone formal security analysis.
- OpenID Foundation, FAPI Working Group specifications. Source for the family of FAPI specifications, the FAPI 2.0 Security Profile and the FAPI 2.0 Attacker Model and Message Signing, and for the working group remit of providing an interoperable, high security profile of OAuth 2.0 for protecting financial and other high value application programming interfaces.
- IETF RFC 9126, OAuth 2.0 Pushed Authorization Requests. The mechanism FAPI 2.0 makes mandatory, in which a client registers the authorization request directly with the authorization server over an authenticated back channel and receives a request reference to use at the authorization endpoint, so the parameters the user approves cannot be tampered with or leaked in the front channel.
- IETF RFC 8705, OAuth 2.0 Mutual TLS Client Authentication and Certificate Bound Access Tokens. One of the two mechanisms FAPI 2.0 allows for sender constraining, in which the access token is bound to the client certificate used on the TLS connection, so a resource server accepts the token only from a caller that can prove possession of the same certificate.
- IETF RFC 9449, OAuth 2.0 Demonstrating Proof of Possession (DPoP). The other sender constraining mechanism FAPI 2.0 allows, in which the client holds a key and signs each request, so the access token is bound to that key and a copy of the token alone cannot be replayed by a party that does not hold the key.
The requirements of the FAPI 2.0 Security Profile, its section numbers, the mechanisms it mandates, its final status as of 22 February 2025, and its reliance on a formally analysed attacker model were read directly from the OpenID Foundation specification, with the OpenID Foundation announcement of July 2025 used for the final status, the conformance tests, and the companion Message Signing profile. The underlying mechanisms follow the IETF standards FAPI 2.0 composes, pushed authorization requests (RFC 9126), mutual TLS and certificate bound access tokens (RFC 8705), and DPoP (RFC 9449), with PKCE (RFC 7636) and the iss parameter (RFC 9207) named in the profile itself. No embedded posts from X appear here, because no public post on this topic could be verified as current, relevant, and authentic at the time of writing, and an unverified embed is worse than none. Nothing here is security, legal, or engineering advice for a specific system. The ids, names, and dates in the worked example and the record are placeholders for shape rather than real values, and how a given team secures a given connection depends on its own providers, systems, and risk.