Using JWTs and OAuth2 to Secure APIs and Microservices
In this post we specifically discuss usage of JSON Web Tokens (JWTs) with OAuth2. JWTs can certainly be used without OAuth2, by utilizing any other authentication method (e.g. Basic Auth over HTTPS), but in this opinionated post we assume OAuth2 is usually a preferred method for performing API client authentication and are mostly interested in how using JWT can improve an OAuth2 implementation.
We further assume that the reader is familiar with OAuth2. If this is not the case, you may want to consult with Alex Bilbie's wonderful OAuth2 reference.
Shortcomings of OAuth2's Opaque Bearer Tokens
Various OAuth2 workflows all authenticate API clients and if auth passes, a server issues API client a bearer token. API clients use the bearer token to make subsequent API calls when accessing protected API endpoints.
OAuth2 spec doesn't define the format of the bearer token or assign it any meaning. In a lot of legacy implementations, bearer token is just a unique string. To validate it (and determine any scopes associated with the token) API endpoint may need to verify the token, provided by a client, using one of two means:
- Calling Authorization service and supplying the token. This leads to a network sub-call for every API call.
- Assuming API Gateway is always in front of it, validating the token for the API at every request. This approach will usually lead to a data-lookup by the API gateway for each API call.
In the two scenarios, either service itself or the API Gateway are performing a database lookup to validate the token, at every API call. The lookup can be cached, of course. Regardless, we are either sacrificying scalability (server memory used for caching is limited) or speed (network calls can be expensive).
JWT tokens give us a way of validating tokens that use some CPU but do not require any RAM or a network call. Since CPU can easily be scaled horizontally it can be a very interesting solution, in many cases.
Using JWT for Token Validation
Instead of a opaque string, a bearer token can be a JSON Web Token (JWT).
A JWT is basically a base64-encoded JSON object represented as a string of
characters. Since the token is actually a JSON object, it can carry all kinds of
information ("claims"). Specifically, JWT can carry such "claims" as:
client_id of the API caller and all OAuth2 scopes associated with
How can we trust that information in JWT is valid and a client is not claiming something it is not authorized for, or pretending to be someone else? We can trust this because JWT tokens are cryptographically signed.
To be very clear: JWT tokens are not encrypted! A JWT can be decoded by anybody. Which means: you should never include private information in it, i.e. never encode passwords or secrets in a JWT! However you can verify whether JWT is properly signed by an OAuth2 server. This can be done in two ways:
- Using a shared secret key (symmetric approach)
- Using private/public key combo (assymetric approach).
The latter is preferred. In the assymetric approach, OAuth2 Authorization Server signs JWT token with a private key. All your API endpoints can possess corresponding public key (which is not secret and can be widely distributed). Using the public key, API endpoints can verify that the JWT was indeed issued by a trusted authentication server (e.g. OAuth2 server) and therefore claims in the token (the JSON object) can be trusted as made by the authorized server.
As you may have already guessed, the huge benefit of the JWT approach is its scalability: an API endpoint verifying the JWT only needs to posess proper public key, but doesn't need to look anything up in a data storage or use memory to cache lookups. Furthermore, public keys are not secrets and don't need to be secured in any special way, making logistics of the verification easy.
JWT tokens can be an extremely good solution for securing inter-service communications in a Microservice Architecture, where speed and scalability can be of paramount importance. Since JWTs can be issued by any authentication server/process, using JWTs also allows entirely bypassing an API Gateway for inter-microservice communications if and when appropriate.
JWT Criticism and Security Considerations
JWTs are not without limitations. Since API endpoints don't consult a central server, central revocation of credentials (an important feature of OAuth2) can be challanging. JWT tokens do have expiration time and are not issued in perpetuity, but if you detect that a client is compromised you cannot centrally and immediately revoke it, at least: not until the latest issued JWT expires.
Due to this limitation, it is recommended that expiration times of JWT tokens issued are set to reasonably short periods. JWT supports token expiration out of the box. How short the expiration times should be will depend on your use-cases and requirements.