A Primer on JSON Web Tokens

Illustration: A Primer on JSON Web Tokens

JSON Web Token (JWT) is a data exchange format that can be used to ensure the authenticity of a message. JWTs are widely used in the industry — they were first proposed in 2010 and then standardized in RFC 7519, and they’re used in the OpenID Connect specification. In this blog post, I’ll explain what they are and why they’re useful, along with how we use them at PSPDFKit.

What Are JWTs?

JWTs are text tokens that encode a JSON object containing claims, or statements, that the receiver of a JWT trusts to be true. There are several standardized claims, e.g. expiration time, issuer, and audience (you can find a complete list here). But you can put any kind of data inside a JWT — some kind of identifier, a list of permissions, or even the name of your pet:

1
2
3
4
5
6
{
  "exp": 1601027965,
  "user_id": "1acf7e0a-3ac0-4abe-befb-21ed9f83d1de",
  "permissions": ["read"],
  "pet": "Sparky"
}

JWTs support verification of the authenticity of a message — you can prove the message has not been tampered with and you can verify the source of the message. Note that authenticity does not imply encryption; encryption means that a clear text representation of the message is hidden, and it’s not possible to see it without knowing the cipher and some sort of encryption key. This isn’t the case with JWTs. Anyone can decode a JWT token and see its contents.

Compare this to the real world. Imagine you receive a snail mail letter from your friend, and it’s been signed with a pen at the bottom. When you see that signature, you know that your friend is the source of that message (or not, if the signature doesn’t look right). However, anyone who opens an envelope can also read the letter, because it’s not encrypted.

❗ Important: Since JWTs aren’t encrypted, it’s best not to store any sensitive data (like a credit card number) inside them. It’s also a good idea to transfer them over an encrypted medium, like an HTTPS connection.

Verifying JWTs

The authenticity of a JWT can be proven by checking its signature. The signing algorithm used is specified in the JWT header, which is another JSON object:

1
2
3
4
{
  "typ": "JWT",
  "alg": "HS256"
}

The "alg" field holds the signing algorithm — in this case, HMAC SHA-256.

There are a few standardized signing algorithms that can be used, but we can divide them into two groups:

  • Symmetric algorithms, where both the message issuer and the recipient share a secret key that’s used to sign and verify the signature.
  • Asymmetric algorithms, where the issuer signs the message with their private key that only they know, and the recipient verifies it with a corresponding public key.

In the first case, the recipient verifies that the message has been created by an issuer who knows the shared secret key. In the second case, the recipient verifies that the message has been signed by someone who holds the private key corresponding to the public key that the recipient knows.

Putting It All Together

When you have a JWT header, claims, a signing algorithm, and the signing key (either a shared secret or a private key), you can create a JWT using the following algorithm:

1
2
3
payload = base64Url(header) + "." + base64Url(claims)
signature = sign(payload, key)
jwt = payload + "." + base64Url(signature)

base64Url here means base64-encoding in a URL-safe way so that the result can be embedded in URLs without an issue. First you encode the header and the claims, and then you build the payload by concatenating them with a dot. Next, you create the signature by signing the payload. Finally, you append the base64url-encoded signature preceded by a dot to the payload. The result is a JWT:

Copy
1
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoiMWFjZjdlMGEtM2FjMC00YWJlLWJlZmItMjFlZDlmODNkMWRlIiwicGV0IjoiU3Bhcmt5IiwiaWF0IjoxNjAxMDMyMTc4fQ.UW736TXVkfWx3VLJ7DoUVl3wwTy5rV63MMtj1og1-L0

ℹ️ Note: Remember that even though the JWT looks obfuscated, it’s not encrypted. You can see that by taking a header or claims from the JWT above and running them through base64 decoder (e.g. base64 -d in a shell) or pasting them in the JWT debugger at https://jwt.io. Anyone can read the contents of the JWT.

Let’s wrap up this section with a recap of what JWTs are:

  • A data exchange format
  • where data is not encrypted
  • that includes a signature used to prove the authenticity of the message (the source and data integrity)

With that in mind, let’s get to the next part where I explain when and how you can use JWTs.

When and How to Use JWTs?

So far I’ve been presenting JWTs as a data exchange format, although in practice, they’re usually used for authorization.

In a typical scenario, we have three parties:

  • A subject, who wants to access a protected resource.
  • An issuer, who can verify the identity of the subject (i.e. authenticate them).
  • An audience, which grants access to the protected resource.

Using simpler terms, the subject is a user, the issuer is an application where the user logs in, and audience is some kind of service the user want to access.

The flow looks like this:

  1. The user logs in to the application by providing credentials, proving they are who they say they are.
  2. The application generates a JWT, which contains claims that define the authorization level for a certain user over specific resources. The application signs the JWT with a shared secret key or its private key.
  3. The JWT is handed to the user.
  4. When accessing the resource, the user presents the JWT to the service.
  5. The service, knowing the shared secret key or the application’s public key, can verify the authenticity of the claims in the JWT.
  6. Finally, the service grants the subject access to the resource.

When the signature is valid, the service knows that the JWT has been generated by the application and that a malicious user hasn’t changed any of the claims. It grants access to protected resources based on the information in the claims. However, if the signature is invalid, the service knows that either someone modified the claims or it’s not the application that issued the JWT. In either case, the message isn’t authentic and the user’s request is rejected.

This is a lot like when medieval lords sold documents with their seal on them to merchants and travelers. The documents asserted that the person possessing them was under the lord’s protection when passing through the lord’s lands. The lords didn’t know it back then, but they used JWTs (only back then, this concept was known as Geleitrecht, or right of escort).

Notice that the JWT doesn’t need to be presented to the service every time the user wants to access a resource. For example, after the JWT has been accepted, the service can return a shortlived access token or a stateful session ID that’s used for authenticating additional requests.

Since JWTs are just pieces of data that can be exchanged between parties, and since their verification requires no communication between the audience and the issuing application, they’re best applied in situations where you don’t control both the issuer and the audience. An example of this would be when you write the code for the issuer, but the audience is a piece of software created by a different team or built by a different organization.

The only thing the audience needs to know is a shared secret key or a public key of the issuer. That’s enough to make sure claims in the JWT have been provided by an issuer the audience trusts. This is also why JWTs are widely used in federated authentication schemes like OpenID Connect, wherein a third party is used to validate the subject’s identity.

Misuse

The most often cited example of JWT misuse is using them instead of stateful sessions stored in the database. This blog post by Randall Degges highlights the reasons why session IDs stored in a cookie are almost always superior to JWTs. Check it out!

JWTs at PSPDFKit

You now know what JWTs are, why they’re great, and why they’re not always that great — but what does this have to do with PSPDFKit?

JWTs are an authorization mechanism used in PSPDFKit for Web when used with PSPDFKit Server. To put it in the context of what we’ve discussed so far, your application is an issuer — it generates JWTs and signs them with its private key. The JWT’s claims contain information about which document and layer the user can access, what their permissions are, and for how long the token is valid. It can also include a user ID, which is used to associate annotations created by the user with them:

1
2
3
4
5
{
  "exp": 1601040033,
  "document_id": "7KRH488QQWSZVT2X90FFVTB1NE",
  "permissions": ["read-document", "download"]
}

The user is the JWT’s subject, who passes the JWT to PSPDFKit for Web:

1
2
3
4
5
PSPDFKit.load({
  serverUrl: https://pspdfkit.your-domain.com,
  documentId: "7KRH488QQWSZVT2X90FFVTB1NE",
  authPayload: { jwt: jwt }
})

When the Web SDK starts, it sends the JWT to PSPDFKit Server’s authentication endpoint for the specified document ID. Server first verifies the authenticity of the JWT by checking the signature using the configured public key. If the signature is valid, it checks that the ID of the document that the Web SDK tries to authenticate against matches the document ID your application stored in the JWT’s claims. When they match, the Server sends a more compact access token to the browser, which is then used to authenticate any requests later on.

But the new kid on the PSPDFKit block, Processor, also uses JWTs. Processor allows you to perform all sorts of operations on a document — like editing pages, adding watermarks, and converting images and Office documents to PDF. In contrast to PSPDFKit Server, its HTTP API is not protected by a shared API token, but rather with JWTs. Processor validates the authenticity of the JWT and uses claims to make sure a user was granted permission to process specific files or to apply particular operations. Thanks to this granular authorization scheme, you can expose the Processor endpoint to the internet and call it directly from your Web or mobile applications.

Wrapping Up

You now know what JWTs are and how we use them at PSPDFKit. I hope this post shed some light on when you might consider using them in software you write. And if you want to try using JWTs in practice, make sure to check out PSPDFKit Processor!

PSPDFKit Processor

Download the free 60-day trial and add it to your app today.