Skip to content
Alexander Holbreich
Go back

JSON Web Tokens are made for Microservices

(updated )

Modern applications increasingly consist of microservices. These applications need authentication and authorization, and a client often communicates with several backend services where every interaction must be checked.

Intro

Authentication and authorization logic typically uses tokens. A token is issued by some Authorization Service after it authenticates the user, for example with a username and password. The same service may also know the user’s rights, roles, scopes, or other authorization attributes.

Some systems use opaque or reference tokens: the token itself does not contain all information required by the receiving service. The service has to ask the authorization system, or an introspection endpoint, what the token means. OAuth 2.0 can work this way, but OAuth does not require opaque tokens; access tokens can also be JWTs.

Auth Flow

As shown in the diagram, once acquired, the access token is used in following service calls. The receiving service must check the validity of the token and often needs identity and authorization data such as the user id, tenant, roles, or scopes.

Reference tokens

When a token is modeled as a reference token, every service usually has to contact the authorization system, because the token is only a temporary reference to identity and authorization data. There must be a routine that lets the service fetch everything it needs using that reference.

In the age of monoliths1 and stateful backend workers this approach probably had more advantages than disadvantages.

In microservice-style architectures, services tend to be stateless. At least, services that do not share common mutable state are often the desired goal. Also, when services become small and simple, the complexity is not gone; it has moved to the level of interactions and communication. We need to be careful there.

Imagine ten or more backend services behind one single-page application. If all services have to talk to one authorization service just to verify a token on every request, this has consequences.

Typical disadvantages of reference-token validation in such an architecture are:

Reference tokens are still a valid design. They are often the better choice when immediate revocation, central policy enforcement, or very small tokens are important. But they are not the only option.

JSON Web Tokens: self-contained tokens

Another approach is a self-contained token, sometimes called a token “by value”:

A self-contained token carries the information needed by the receiving service, at least for authentication and often for coarse-grained authorization.

Such a token can be validated locally by the microservice itself. Cryptography is used to protect the token against tampering.

This is exactly where JSON Web Tokens (JWTs) are useful.

JSON Web Token is standardized in RFC 7519. A JWT is a compact, URL-safe representation of claims. In practice, when people say “JWT”, they often mean a signed JWT, technically a JWS. The service can verify the signature and then read the claims.

This enables architectures that are less coupled to the authorization service at request time. A service can validate the token locally and continue to operate even if the authorization service is temporarily unavailable. The authorization service itself can also become simpler because it does not need to answer introspection calls for every request.

However, some complexity remains. There are two common ways to sign tokens:

JWT crypto details discussion

With a shared secret, you get the operational complexity and risk of secret distribution. See also Symmetric Key Cryptography.

This is where asymmetric algorithms help. The issuer keeps the private key secret. Resource services receive only the public key, often via a JWKS endpoint. A service that only has the public key can validate tokens but cannot mint new ones.

The main difference between RSA, ECDSA, and EdDSA is in ecosystem support, key size, signature size, and performance. ECDSA and EdDSA can produce smaller signatures than RSA, which helps when tokens travel in HTTP headers.

Warning: a signed JWT is not encrypted.

Base64URL encoding only makes the token transport-friendly. Anyone who obtains the token can decode and read the claims. Do not put secrets, passwords, personal data, or unnecessary internal details into a normal signed JWT. If confidentiality is required, use JWE or avoid carrying the data in the token at all.

JWT code example

Here is a small HMAC-based JWT example using the JJWT Java library. The original version of this article used jjwt 0.7.0; if you copy the example today, use a current version.

<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.7.0</version>
</dependency>

Code that generates a token:

public String createToken(long userId, String userEmail, long ttlMillis) {
//Get current timestamp
long nowMillis = System.currentTimeMillis();
// Let's set the JWT Claims
JwtBuilder builder = Jwts.builder().setIssuedAt(new Date(nowMillis)).setSubject(String.valueOf(userId))
  .setIssuer("MyAuthoritativeService")
  .claim("email", userEmail)
  .signWith(SignatureAlgorithm.HS256, jwtKey)
  .setExpiration(getExpDate(nowMillis, ttlMillis));
// Builds the JWT and serializes it to a compact, URL-safe string
return builder.compact();
}

Here token is generated for userId and userEmail that are “backed in” as two claims. The getExpDate(nowMillis, ttlMillis) method produces Expiration Date. Here jwtKey is used as a pre-shared key. The resulting token may look like this:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwiZW1haWwiOiJteXVzZXJuYW1lQGdtYWlsLmNvbSIsInJvbGUiOiJtYXN0ZXIifQ.Le-mu-DstBdPhSoaeaN9Rl_TlIe05NLpsy2vs_q8c74

Validation and parsing:

Jws<Claims> claims = Jwts.parser().setSigningKey(jwtKey).parseClaimsJws(token);
String userId = claims.getBody().getSubject();//Subject is user ID in this example.
Object email = claims.getBody().get("email");

The parser verifies the signature and validates required claims. Parsing can fail because the token is malformed, the signature is invalid, the token has expired, or a required claim does not match. Treat such failures as authentication failures.

JWT details

JWT is standardized by RFC 7519. A signed JWT consists of three parts:

I hope you like it. I will propose two token solutions with JWT next time.

Footnotes

  1. Mentioning “Monoliths age” I mean the past. Maybe that term is too harsh.

  2. See also Symmetric Key Cryptography


Share this post on:

Archived comments (10)

These comments were migrated from Disqus and are no longer accepting replies.


Previous Post
AWS for Docker containers: first impressions
Next Post
How modularity enables Agility