How to Bring Financial-Grade Security to APIs Michał Trojanowski October 13, 2022 These days, everyone in IT talks about security, which obviously is a good thing. The security of applications and data is essential, but it’s also a complex topic. Some solutions are more secure than others, meaning they are only fit for specific applications and businesses. One industry that requires strict security measures is the finance industry. We trust financial companies with sensitive information and our assets and expect them to implement strict security. Financial companies are embracing APIs more and more every year. This is partly due to government regulations requiring those companies to share data using APIs. Open banking regulations like PSD2, UK Open Banking, Open Banking Brazil, and more, now require institutions in different parts of the world to expose APIs and enable other parties to easily access resources. These regulations also have another important aspect — they require a certain level of security for the exposed APIs. They must also describe the technical measures that should be implemented by institutions to meet those high-security standards. Many of them rely, at least in some part, on security standards created by the community, such as the FAPI security profile for OAuth. Using standards and outlining profiles for implementing strict security in APIs brings the additional benefit that these regulations can be applied beyond the financial world. These solutions may be implemented in any industry that requires strict security — they aren’t limited to financial institutions. That’s why we talk about “financial-grade” security, not just “financial” security. For example, medical applications or any applications that handle sensitive data can benefit from financial-grade security features. Financial-grade security is not limited to the finance industry. In this article, I’ll explain a few features commonly used in financial-grade systems. We’ll also consider how these financial-grade solutions can be utilized to secure our API-based platforms. Mutual TLS Mutual TLS (mTLS) is a specific usage of the Transport Layer Security protocol, where both parties identify themselves with verifiable credentials. In a standard TLS connection, it’s just one party that is interested in verifying the identity of the other end. For example, when TLS is used to access a web page, the browser verifies the identity of the server. The server is not concerned with the browser’s identity — it allows any client to connect. In an mTLS connection, the server will also require the client to present valid credentials that can be used to identify it. The server then only sends data to a client it knows and trusts. This mutual trust can be used for various purposes in the API landscape. For example, it can serve as a more secure way of client authentication when a confidential client calls an OAuth server to obtain access tokens. It can also be used as a way to implement sender-constrained tokens. Sender-Constrained Tokens The common way of using tokens in APIs is to go with bearer tokens. This means that any party that presents a valid token to the API will be granted access. The API isn’t concerned with the identity of the calling application, and in fact, it has no way of verifying that. This means that when someone manages to steal an access token, they can use it, as the API can’t tell that the token was originally issued for a different client. Sender-constrained tokens are meant to alleviate this issue. When an authorization server issues this type of token to a client, it binds it to that client’s credentials, e.g., a TLS certificate. When the token is used in a subsequent call to an API, the validity of the token is based not only on the usual properties, like the signature, expiration, or audience. The API also verifies the client’s credentials and checks whether they match the ones present in the token. Only clients that were initially issued the token can use it to access APIs. Sender-constrained tokens (also called proof-of-possession tokens) can be implemented in a few different ways, and some standards can be used in implementations. RFC 7800 adds a confirmation (“cnf”) claim to JSON Web Tokens (JWTs). This claim contains a value that can be used to verify the sender’s ownership of the token. If mTLS is used in connections between the client and the API and between the client and the authorization server, then the token can be bound to the certificate used in the connection, and the cnf claim can then contain a reference to the client’s certificate. If your system is not able to support mTLS connections, then the Demonstration of Proof-of-Possession standard (DPoP) can be used. Pushed Authorization Requests (PAR) RFC 9126 defines Pushed Authorization Requests (PAR) — a way of making confidential and integrity-protected authorization requests. Normally, an OAuth authorization request is a GET request made through the browser with a number of query parameters, like the client ID, requested scope, and redirect URI. This approach has a few issues, though: The authorization server has no means of verifying the integrity of the request. A sophisticated attack could change some parameters, and the authorization server wouldn’t realize it. The authorization request is not authenticated. Any party can issue such a request for any client. Even though a malicious party won’t be able to eventually get the access token, since the legitimate client’s credentials are needed for that, it will be able to gather some information while performing the flow. PAR aims to solve the above problems by changing how authorization requests are initialized. When using PAR, the client first makes a backchannel, authenticated request to the authorization server and sends all the parameters that would normally be used in the query string. As the request is authenticated and made through the back channel, the authorization server is now sure of its authenticity and integrity — no man-in-the-browser can occur as the browser is not involved at this step. The authorization server can also quickly reject any request if the client doesn’t authenticate properly. The client then returns a request ID, which is a reference to the authorization request’s parameters. The client then uses that request ID as the sole query parameter sent with the authorization request through the browser. Thanks to this, the original parameters are not exposed to the browser and can’t be tampered with. JWT-Secured Authorization Response Mode (JARM) Even though the JWT-Secured Authorization Response Mode (JARM) is still a draft specification, it is mature enough to be implemented and is even used in the final version of the Financial-grade API security profile for OAuth and OpenID Connect. This specification adds new response modes to the OIDC code flow in which JWTs are returned instead of plain data. In a usual code flow, the authorization server sends back parameters (such as the authorization code, state, etc.) directly to the client, usually through a browser redirect. The client then uses these parameters to obtain access and refresh tokens. This approach has a few issues: The client cannot verify from which authorization server these parameters come. If the client can perform authorization against a few different servers, it can lead to a mixup attack, where the client is tricked into sending the authorization code to an attacker’s server. The client can’t verify the integrity of the response parameters. Since the parameters are passed through a browser, they are vulnerable to a man-in-the-browser attack. JARM helps to solve the above issues. In this response mode, the client receives the parameters in a signed JWT. This allows it to verify the integrity of response parameters through the JWT’s signature. The response JWT also contains an issuer claim that allows the client to discriminate which authorization server issued the given response. Conclusion Financial-grade security is not just about the finance industry. It can prove useful in many industries where a high level of security and privacy is essential. Have a look at the FAPI security profile, and I’m sure that you will find features that will help make your APIs more secure. If you’ve ever considered it a high risk that anyone could steal your access tokens and gain access to your APIs, then have a closer look at sender-constrained tokens. They are a bit more complicated to implement but give that additional level of certainty about who is using the token that you’ve issued. If your concern lies with the flaws of browsers and their part in obtaining access tokens, then consider PAR and JARM. Implementing them in clients can be done relatively easily, and it will safeguard you from the hostile environment of the browser.