While we often use the terms interchangeably, authorization and authentication are two separate functions. Authentication is the process of verifying who a user is, and authorization is the process of verifying what they have access to. Programmers who need to access the configurable elements of an API need authorization for access, but their authentication credentials should not allow them to change the way the two computers interact with one another.
Authenticating a client or a user to an API is crucial because the API uses that client or a user identity to decide whether an operation is permitted. Client authentication is vastly different from user authentication. Clients are automated, so authenticating them on every request to the API is not a problem. Users, however, do not want to have to provide credentials for every API call. That’s why user authentication typically happens once during a session. Once authenticated, a user’s authenticated state is propagated on subsequent requests.
Application programming interfaces (APIs) enable computer programs to communicate with each other. And because APIs hide the internal details of how a system works while providing a safe surface where systems can communicate and perform operations, the mechanism programmers use to access APIs is of crucial importance.
API security rests on proper configuration of authentication and authorization mechanisms. Let’s look at a recent breach as an example. In July 2022, researchers discovered a set of 3,207 mobile apps leaking valid consumer keys and consumer secret credentials for the Twitter API. When developers integrate mobile apps with Twitter, they use authentication keys to authorize their apps to interact with the Twitter API. When users associate their Twitter accounts with the mobile app, the same keys will enable the app to act on behalf of those users, allowing them to log in via Twitter to create tweets, send DMs, and use the site.
The kind of data leak found by the researchers occurs when app developers embed their authentication keys in the Twitter API during development but forget to remove them when their mobile app is released. In this case, these stored credentials could allow a hacker to access users’ direct messages, remove or add followers, access account settings, change display pictures, and perform all tweet actions including create, delete, retweet, and like. They could also allow a threat actor to use these exposed tokens to create Twitter verified accounts with large numbers of followers to promote fake news, malware campaigns, cryptocurrency scams, and more.
The OWASP foundation has a long-standing tradition of releasing a list of the 10 most dangerous web application security vulnerabilities. This list is based on attacks observed on real-world systems as well as expert knowledge, and it’s updated every few years. Since 2019, OWASP has also released a dedicated OWASP API Security Top 10, focusing solely on API-based vulnerabilities. The similarities between the two lists are striking. In Figure 1, they become especially clear when we map the traditional OWASP Top 10 items (right) to the API Security Top 10 (left).
Figure 1: Comparing similarities between the OWASP Top 10 and the OWASP API Security Top 10.
It’s clear that authentication and authorization are challenges for all kinds of applications.
There are a few authentication and authorization best practices to help you make sure your APIs are secure. Keep in mind that this is only an introduction. Be sure to see the deep dive into API authentication and authorization in our Essential API Security course.
Static configuration—associating a specific public key with a client—is the most straightforward configuration, but it’s largely restricted to closed systems where clients are known upfront. Public keys can also be stored in a centralized key store where the API can retrieve them. Adding a new client just requires that you register a new public key with the centralized key store. This approach allows for more flexibility than static configurations since clients do not have to be explicitly known to the APIs.
Many systems use a public key infrastructure (PKI) to handle keys. In a PKI-based system, a trusted central authority confirms the trustworthiness of a client key, typically by issuing an X.509 certificate. The API trusts this authority, so it also trusts certificates issued by this authority. The most well-known deployment of this system is the use of TLS/HTTPS on the internet.
Most of today’s internet traffic relies on transport layer security (TLS) to provide security guarantees. TLS ensures the confidentiality and integrity of the data being transferred. Additionally, a typical TLS connection also provides authenticity assurances about the remote server that the client is connecting to. For example, when you visit https://www.synopsys.com, TLS helps your browser verify that the server responding is indeed a legitimate server hosting the Synopsys website.
An important component of TLS is that it supports two-way authenticity, meaning that the client is also required to prove its identity to the server. This mode of operation is known as mutual TLS (mTLS). TLS fundamentally relies on public/private key pairs and X.509 certificates, so it is well-suited to implement a key-based client authentication mechanism.
Since TLS is a network layer protocol, applications typically do not implement any of the details in code. Instead, they rely on existing libraries to set up a TLS connection. This pattern is one of the strengths of using mTLS for client authentication: There is little to nothing to implement in the application itself.
JSON web tokens (JWTs) are often used to implement key-based client authentication. The client generates a JWT and signs it with a private key. The API then validates the JWT with the client’s public key and uses the embedded claims to make an authorization decision. Due to the nature of JWTs, there are quite a few degrees of freedom when implementing a client authentication scheme.
For example, RFC 7523 describes how to use a JWT for authenticating a client to the authorization server. This scenario is slightly different from using JWTs to authenticate to an API, but it nonetheless provides a good example of how JWTs handle authorization.
A signed JWT can only be used to call a specific endpoint within the validity window between the issued-at and expiration timestamps. The JWT is typically included in the request using a custom header.
When dealing with multiple clients, it is also recommended to include a key identifier in the JWT’s header. This helps the API retrieve the correct key for this particular client from a key provider. After verifying the JWT signature, the API should ensure that the claim corresponds to the identity used to retrieve the key.
So, should you use mTLS or a custom JWT-based client authentication scheme?
Overall, mTLS is well-supported and offers robust security properties. However, because mTLS operates at the network layer, it can be too rigid to guarantee the necessary security properties. For example, server-side middleboxes such as reverse proxies, API gateways, and traffic inspection devices require access to the data of network requests, so they have to terminate the TLS connection. This means the TLS connection is established between the client and the middlebox. After inspection, the middlebox sets up a new connection with the API to forward the request information. However, the presence of the middlebox erodes the security benefits of using mTLS.
A JWT-based authentication mechanism does not suffer from this inconvenience. When using JWTs, the middlebox still handles TLS traffic but forwards the entire request to the API, including the JWT provided by the client. When the JWT contains metadata about the request, it guarantees the request’s integrity, preventing the middlebox from tampering with the request information.
For these reasons, mTLS is recommended for native clients that access server-side APIs that reside in a tightly controlled environment. When the limitations of mTLS impact the end-to-end security of your architecture, the use of JWTs provides a viable alternative.
JWTs and mTLS both have their respective drawbacks. Keeping track of authentication state on the back end makes it harder to scale applications up. Making the back end stateless is beneficial to scalability but reduces control over authentication state, harming revocability.
Additionally, both approaches are designed for first-party scenarios, in which the client and back end trust each other. Implementing such an architecture for third-party clients without sacrificing security can be a challenge, and that’s where OAuth 2.0 comes in.
The goal of OAuth 2.0 is to enable clients to access resources, such as APIs, on behalf of a user. OAuth 2.0 is an extensive framework that enables authorization in complex architectures, both for first-party and third-party scenarios.
In an OAuth 2.0 architecture, the client interacts with the authorization server to obtain an access token. This token represents the client’s authority to access APIs on behalf of the user. The client includes this token on any request to the API, allowing the API to make authorization decisions.
OAuth 2.0 supports two types of access tokens: reference tokens and self-contained tokens. When using reference tokens, the API asks the authorization server to translate the identifier into the associated state, which can be considered a stateful approach. When using self-contained tokens, APIs can handle tokens independently, which corresponds to a stateless approach. OAuth 2.0 also provides control mechanisms to reduce the lifetime of self-contained access tokens, reducing the impact if they are stolen.
Making user-based authorization decisions in APIs requires having user authentication information. Users typically authenticate once, after which their authentication state is propagated on every request. The specific mechanism to track authentication state is highly dependent on your application’s architecture.
Authentication and authorization are core to the security of APIs. Because they play different roles, it’s important to configure them correctly. But together they ensure that the correct and legitimate users have access to your webapps, and that they have the correct and legitimate permissions to accomplish the tasks they need to perform.