Security Best Practices for Managing API Access Tokens
This article is featured in the new DZone Guide to Dynamic Web and Mobile Development. Get your free copy for more insightful articles, industry statistics, and more!
Modern applications, both web-based and native, rely on APIs on the backend to access protected resources. To authorize access to those APIs, a request must include some kind of access token or key. This article focuses on security best practices for access token management — for API providers and application developers alike.
Let's Talk About Trust First!
When dealing with security, a single rule prevails: trust no one. If you're an API provider, you can't trust that the application invoking the APIs is the one you expect, that the token you received has not been stolen, or that the communication between client and the server has not been intercepted. On the client-side, you can't trust that the application will not be decompiled (exposing embedded secrets), that the application storage will not be compromised through an XSS attack, or that your users are not being fooled into submitting forged requests.
This implies that you must put into place proper measures to securely obtain, store, and manage the security tokens required to invoke backend APIs.
Additionally, you may think your APIs are safe if you have never publicly advertised them. To you, they feel private because they are only used by your enterprise applications. However, if they can be used from a mobile application, they are on the public internet, and thus public. Any API exposed outside your enterprise network must be considered public.
Obtaining Tokens an API Keys
When it comes to using an API, you are usually offered two choices: pass a static piece of information together with the API call or obtain that piece of information dynamically prior to invoking the API. This piece of information is usually an access token or API key. BasicAuth is still used for some APIs for legacy reasons but is deprecated as a mainstream solution.
When designing the security aspects of your API, you must choose wisely how your API consumers access it. As per usual with security measures, the induced risk is the key factor to take into account. Securing an API that only allows for consulting weather data is very different from securing a banking payments API.
While using an API key is easier for the developer, it does not give the same level of security as an access token obtained with two-factor user authentication and the proper identification of the client application. Moreover, an API key does not carry any information about the user and can't be used at the backend level to decide which operations the API consumer is allowed to invoke. Finally, API keys never expire unless revoked by the API provider.
OAuth was created to address these drawbacks:
The application accessing the resource is known (using client application credentials).
The API provider can define scopes to limit the access to certain operations (you can GET a catalog entry, but you can't PUT a new catalog entry, even with a valid token).
Tokens have a limited lifetime.
Let's Start With Some Terminology
The OAuth terminology can sometimes be confusing. In the table below, we present a mapping from practical, development focused terminology to OAuth terminology.
Practical Name | OAuth Terminology | Description |
Application | Client | The client is the application accessing a resource on behalf o fa user. |
Web server apps | Confidential client | An application running on the server side and capable of safely storing an application secret. |
Single-page apps/browser-based apps/mobile apps | Public client | An application entirely running on the client-side or on a device that cannot safely store an application secret. |
API | Resource server | The API is the means to access the resources belonging to the user (e.g. a bank account). |
OAuth Server | Authorization server | The OAuth server is in charge of processing the OAuth token management requests (authorize access, issue tokens, revoke tokens). |
User | Resource owner | The person granting access to the resource the application is trying to access. |
Access token | Bearer token | The access token authorizes the application to access the API. |
Opaque vs. JWT
OAuth does not mandate the access token format, and as such, depending on the OAuth server implementation, the access token could be opaque (typically a long string carrying no information)or a JSON web token (JWT).
The key advantage with JWTs is the ability to contain claims, or information about the user, which the backend services can use to make business logic decisions.
Learning the OAuth Dance
OAuth grant types define how a client can obtain a token. Our team commonly refers to this as the "OAuth dance." There are many ways to dance in the OAuth world, but there is only one you must learn: authorization code. Other grant types can be useful in some circumstances, but the authorization code grant type is the recommended way to obtain an access token for all types of applications: web apps, native apps, and mobile apps.
For public clients and mobile apps, in particular, an additional security measure is recommended to prevent the theft of the authorization code. This security layer is described in the Proof Key for Code Exchange standard (PKCE, pronounced "pixy"). You can learn more about PKCE and how to use it here. If you are an API provider, make sure your OAuth server supports this option.
You should use special care with the Resource Owner password grant: being the simplest to implement, it's quite attractive. However, since its core requisite is client-server trust, you probably should never use it.
Token Management Recommendations
Beware of OAuth App Credentials Leaks
Storing your application code in GitHub? Are your OAuth app credentials stored there, as well, and, in particular, the client secret? This is the number-one source of credentials leaks today. If those credentials are stolen, anybody can pretend to be you. If you believe credentials could have been compromised, regenerate them immediately.
Additionally, never put your client secret in distributed code, such as apps downloaded through an app store or client-side JavaScript.
Don't Hardcode Tokens in Applications
It can be tempting to simplify code to obtain a token for a long period of time and store it in your application. Don't. Do. That.
Treat Tokens as You Would Treat Passwords
Tokens are the door key! Token and API keys allow anybody who has them to access a resource. As such, they are as critical as passwords. Treat them the same way!
OAuth Is Not an Authentication Protocol
OAuth is about delegating access to a resource. It is not an authentication protocol (despite the name). Think of tokens as hotel cards. You need to authenticate yourself to obtain the hotel key, but once you have it, in no way does it prove who you are. API providers must not rely on token possession as a proof of identity, as proven by a recent user information leakage.
You really should look at OpenID Connect (OIDC), a complementary specification, rather than trying to implement authentication on top of OAuth yourself. OIDC allows a user to share some aspects of their profile with an application with no need to share their credentials.
Beware of What You Store in JWTs and Who Has Access to Them
JWTs can store plenty of information in the form of claims and can easily be parsed if captured (unless they are encrypted). If you are using JWTs to carry information that is only useful to the backend services, you can take a different approach: JWTs can store plenty of information in the form of claims and can easily be parsed if captured (unless they are encrypted). If you are using JWTs to carry information that is only useful to the backend services, you can take a different approach:
Use an opaque string or basic JWT between the client and the backend.
At the backend, validate the request and inject a new JWT with a payload containing the claims that are consumed downstream. This feature is provided by many API security gateways out of the box.
If you want to use the same token across the entire flow and it can potentially carry sensitive information, encrypt the token payload. This said, never use a JWT to carry a user's credentials, such as passwords!
Validate JWTs Thoroughly
When you receive a JWT on the server-side, you must validate its contents thoroughly. In particular, you should reject any JWT that does not conform to the signature algorithm you expected or that uses weak algorithms or weak asymmetric/symmetric keys for signing. Additionally, you must validate all claims, expiration date, issuers, and audience.
Certain libraries and tools do this for you; others need to be configured properly first; some only do partial checks. Take the appropriate measures depending on the libraries you use.
Don't Store Tokens in Local Storage; Use Secure Cookies
Browser local storage and session storage can be readfrom JavaScript, and as such are not secure to store sensitive information such as tokens. Instead, use secure cookies, the httpOnly
flag, and CSRF measures to prevent tokens from being stolen.
Always Transport Tokens via HTTPS and in the Request Body
In doing so, you are limiting the risk of the token to be captured in flight or to be written into proxy logs or server logs. You should also ensure you only use TLS 1.2/1.3 and the most secure cipher suites across all actors involved in issuing and validating tokens.
Use a Dedicated Browser View to Request Consent Information
Many applications use an embedded user agent, but this should be avoided because it does not allow the user to properly validate to which site they are talking to. Moreover, the app would have full visibility of the credentials the user enters. Some API providers take strong measures against this practice, as encouraged by the OAuth native apps best practices standard.
Conclusion
Access tokens are fundamental to the implementation of modern applications — but must be handled with care. As a backend developer, you must ensure you provide the proper grant type to obtain access tokens, support PKCE for mobile apps, and thoroughly validate JWTs. As a front-end developer, you must control the storage of JWTs and secure app credentials.
References
PKCE
JWT Validation Practices
Native Apps Best Practices OAuth