JWTs: Just What are They?
Published on September 28, 2023

One of the principles that the Developer Experience team lives by when writing tutorials is that you should never assume people know a tech subject matter. It's something I tend to forget and need to remind myself regularly. If you ever feel that pang of guilt on the other side of this equation, I always find it handy to come back to this evergreen Venn diagram:

Venn Diagram crossing what you know vs. what others know in equal massVenn Diagram crossing what you know vs. what others know in equal mass

Working with JWTs constantly, daily, as part of my job, developing our PHP SDK is one of those subjects. Once I'd looked up how we use JWTs, that was that. But, with my "never assume knowledge" principle in place, I realize there are a lot of people who have no idea what they are. Furthermore, some developers with some experience (even in building and consuming APIs) have yet to use or even hear of them. The pace of evolving API technologies tends to be much slower in larger or scale-up businesses than many realize. It's 2023 at the time of writing this article, yet many Banks and Pharmaceutical enterprise entities still use SOAP.

Before we dive into the anatomy of JWTs, it's worth noting that this article involves examples with code. If you fancy using some of our tooling instead to handle creating JWTs, head on over to this article by Benjamin Aronov that shows you how to generate one entirely within the Vonage Dashboard.

The Basics

JWT stands for JSON Web Token. In a slightly confusing acronym spaghetti scenario, it technically stands for JavaScript Object Notation Web Token in its complete form. Up until its introduction in a draft dated July 2011, the most common forms of authentication for web APIs were adding an API Key and Secret unencrypted into a request's query string or Basic Authentication where one of an API Key or Secret (or both) are hashed, usually with base64 encoding and passed as a header. A JWT is a hash included in the header of a request (or could be in the body, but that's less common), replacing older authentication styles.

Existing Authentication: The Cons

So, why the need for a new authentication technique?

Security

This has to be the most significant factor. Sending an API Key or Secret unencrypted in the header (or the query string) for every request opens up a pretty big security vector. There's more traffic with the same credentials open to sniffing, and as soon as those keys are compromised, you have opened up your whole application or account.

There tends to be a trap regarding Basic Authentication - the style where you take {username}:{password} as a string and base64 encode it. Many new developers to API authentication might see the resulting hash and think, "That looks secure!" but the reality is that it's easily decoded into its two secret parts. For example, head to https://emn178.github.io/online-tools/base64_decode.html and paste in c2VlLXRoYXQtd2FzOmVhc3k=.

The only real layer of security you can create when using these methods is to rotate your keys. Depending on your API product, big legacy codebases might not even have the ability to rotate a main key/secret combination (especially if they're tied directly to an account during account creation)

Scope

Previously, with basic types of authentication, new keys would be added to a portfolio on top of your primary keys, which you would typically create with a set of selected scopes. This can result in quite an extensive collection of keys used for various products or specific queries - rotating a large amount of keys periodically can become quite cumbersome.

Portability

As with the scope, fixed secrets like this have the users' permissions, scopes, and API access stored within the server. This makes them static and not flexible when you have a more complex API with many parameters to consider when receiving a request.

Anatomy of a JWT

JWTs aim to address all three of these concerns. Here's how they're structured:

Header Information

When we refer to "header," it's worth noting that this is the header of the JWT and not an HTTP request header.

The server needs information about how the JWT has been created. As a result of this requirement, the header will contain information about how the JWT was created in two keys:

  • alg which in many cases is likely to default to HS256

  • typ which identifies the token as a JWT (as the standard evolves, there are likely to be iterations or new versions of token type)

Payload

The payload of a JWT comes in the form of claims, which are a series of key-value pairs that contain information about the JWT. The most common reserved (or *Registered Claim Names) are as follows:

  • iss: the source of the JWT (application, organization, etc.)

  • sub: a unique value to the issuer, which the other claims refer to in the context of the JWT

  • exp: the expiry date of the JWT

  • iat: when the JWT was created (issued at)

  • jti: a unique identifier for this JWT

JWTs are so handy for authentication because you can add custom claims to them that are relevant to the application that consumes them. Take, for instance, the Vonage JWT - when using our APIs that support JWT authentication, a custom claim, application_id is used. Vonage can check the JWT against the private SSH key on your account (which will have been used at the request client's end to generate the JWT), and if valid, then the request can be quickly routed to the correct application instance within Vonage APIs.

How to Make a JWT: PHP Edition

As the resident PHP developer within my team, I will generate a valid JWT in as few lines of code as possible to show how relatively straightforward it is to get started. Here's what we need:

  • An Application ID from the Dashboard.

  • A copy of the application's private key was downloaded from the Dashboard.

You can get both from the application's settings here:

Screenshot of Vonage Dashboard, accessing an Application's SettingsScreenshot of Vonage Dashboard, accessing an Application's Settings

We will use the PHP JWT creator by Vonage on GitHub for this. To install it, you'll need composer.

composer require vonage/jwt

And now, the code:

<!--?php

require_once "vendor/autoload.php";

$applicationId = "78d335fa-323d-0114-9c3d-d6f0d48968cf";
$privateKey = file_get_contents('private.key');
$generator = new Vonage\JWT\TokenGenerator($applicationId, $privateKey);
$jwt = $generator--->generate();

Done!

To use this in a request, you'll need to add it as a Header. We'll need a PSR-18 compliant HTTP Client to send it. I've chosen Symfony's HTTPClient to show how few lines we can do it in. To install these, add them to your project with composer:

composer require symfony/http-client

And then we're going to POST a request to the Vonage Messages API using our JWT:

use Symfony\Component\HttpClient\HttpClient;

$payload = [
	'message_type' => 'text',
	'text' => 'Using a JWT to send Vonage a request!',
	'to' => '44779999999',
	'from' => '44779499999',
	'channel' => 'sms'
]

$client = HttpClient::create();

$client->request('POST', 'https://api.nexmo.com/v1/messages/', [
	'headers' => [
		'Authorization' => 'Bearer ' . $jwt,
		'Content-Type' => 'application/json',
	],
	'json' => $payload
])

And there you have it! Of course, this is a raw example of how to do it. If you want to do a full integration, I'd recommend using our Core PHP SDK, which handles many headaches. If PHP isn't your flavor of choice, that's OK! We also have Node, Java, Python, Ruby, and NET SDKs!

James SecondeSenior PHP Developer Advocate

A trained actor with a dissertation on standup comedy, I came into PHP development via. the meetup scene. You can find me speaking and writing on tech, or playing/buying odd records from my vinyl collection.

Ready to start building?

Experience seamless connectivity, real-time messaging, and crystal-clear voice and video calls-all at your fingertips.