REST APIs are great because they are logically simple, they don’t keep complex states in memory, they deal with resources (instead of dealing with loose, unconnected functions) making their entire business logic cohesive. I really like them, and since you are reading this, I’m guessing you do too.
That being said, due to the nature and mechanics behind REST APIs, securing them is not always straightforward. What happens after the user submits their credentials? How do you know they’ve correctly logged in on their subsequent requests? You can’t keep a state on your server side to signal that. So what do you do?
In this article, I want to share with you one very powerful yet simple way you can achieve this: using JSON Web Tokens.
JSON Web Tokens are an open and standard (RFC 7519) way for you to represent your user’s identity securely during a two-party interaction. That is to say, when two systems exchange data you can use a JSON Web Token to identify your user without having to send private credentials on every request.
If you apply this into our REST API context, you’ll see how our Client-server interactions can benefit from these mechanics.
In a nutshell, JWT works like this:
- The user/client app sends a sign in request. In other words, here is where your username/password (or any other type of sign in credentials you need to provide) will travel
- Once verified, the API will create a JSON Web Token (more on this in a bit) and sign it using a secret key
- Then the API will return that token back to the client application
- Finally, the client app will receive the token, verify it on its own side to make sure it’s authentic and then proceed to use it on every subsequent request to authenticate the user without having to send their credentials anymore
I know it sounds too simple to be true, doesn’t it? How is that supposed to be secure? Let me explain a bit further.
The token itself, returned by the API is (simply put) an encoded string. It is comprised of three different sections, separated from each other by a dot character:
Each section contains a vital piece of the puzzle. Once decoded, the first two will be JSON representations of data, containing relevant information, and the last one will be used to verify the authenticity of the token:
- The header will contain data related to the type of token we’re dealing with and the algorithm used for its generation. There are several compatible algorithms to be specified here, but the most common ones are HS256 and RS256. It’ll depend on what security standards and measures you’re looking for. In these two examples, one uses a secret key known by both the server and the client and the other one uses a private key used by the server in combination with a public key known by the client.
- The payload will contain data pertaining to the request and the user making it. There are a set of standard key/value pairs that are defined as part of JWT which you can use on your implementation, such as:
- Iss (issuer)- in other words, a way to identify the user making the request
- Sub (subject)- or rather, the subject of the request, in our case, it would probably make sense to include the URI used
- Aud (audience)- it tried to provide some form of identification of the recipient of this token
- Exp (expiration date)- the tokens usually don’t last forever, this is to ensure that whoever is using it, is actually providing a recently generated token
There are other attributes you can add to the payload object defined as part of the standard, but the above ones are the most common ones. Of course, you can use them or just define your own as long as both client and server are in agreement about the implementation.
- The signature is just an encoded string, used by both the server and the client to verify the authenticity of the payload.
Let me now try to tie everything we have covered so far into one example.
Let’s pretend we’re developing a client for our company’s payroll API. This API is meant to issue payments to company employees, retrieve historical information about them and finally, edit the employees’ information.
Additionally (to prevent human error) the developers of the API decided that some of these actions require admin privileges. So we’ll have users with normal access who can only review information and users with super access (admins) who can also issue payments and edit data.
It’s a very basic example, but it should suffice to provide a clear idea of why we do what we do with JWT.
As stated above, any interaction with our secure API would start with a login request. It would look something like this:
And assuming the credentials are valid, the system would return a new JSON Web Token. But let’s go into the details of this token.
Particularly, let’s think about the information inside our payload. Some interesting options could be:
- Iss - containing the username of the logged in user. This is especially useful since we might want to show that in our UI
- Exp - because we’ll only allow this new token to be used for the next 8 hours (which is usually how long users should be using it on a daily basis)
- Admin - boolean describing the role of the user. This comes in handy for the UI, since we’ll need to understand whether to show or hide some UI elements
And to keep things simple, we’ll use an HS256 algorithm for encoding the data, meaning we’ll be using the same secret, both, on our client and our API. For the purposes of this example, our secret will be:
A secret API example
Now, let’s look at how the different sections of our token should look:
Now, in order to create the actual token, we need to encode the above items and then sign the resulting values to add the final piece to the token.
So we have:
Base64(header) = ewoiYWxnIjogIkhTMjU2IiwKInR5cCI6ICJKV1QiCn0K
Base64(payload) = ewoiSXNzIjogImZlcm5hbmRvIiwKIkV4cCI6IDE1NTA5NDY2ODksCiJBZG1pbiI6IGZhbHNlCn0K
HS256(Base64(header) + “.” + Base64(payload), “A secret API example”) = TseARzVBAtDbU8f3TEiRgsUoKYaW2SbhCWB0QlKpdp8
The final token returned by the API is, as you might’ve guessed by now:
And here is where the interesting parts come in — I am going to show you why this is so powerful.
The client application upon receiving this token can decipher it and validate it by grabbing the header and payload portions and signing it on its own (this, of course, is possible because both client and server know the secret phrase). Doing this it can ensure that no one changed the content of the message and that it’s safe to use it.
At the same time, any further request sent by the client app will contain this same token, which in turn, will be validated by the server by re-signing it every time and comparing results with the signature portion of the token.
This would prevent, for example, someone from meddling with the message’s payload and changing the “admin” attribute to “true” allowing a fake (or even a valid non-admin user) to execute a privileged action (such as issuing a payment to some specific employee).
Such an action would modify the payload content to be something like this:
Causing the final token sent by the client app to be the following:
And the signature for this token would have to be:
Which would not match the one sent as part of the message, hence proving that the request had been tampered with.
Hopefully, by now, you’ve been able to grasp the basics of what JWT security entails and you’ve realized that protecting your REST APIs is actually not that difficult. There are of course variations to what I mentioned and showed in this article, but you can look at that on your own by visiting jwt.io. On their site, you’ll have the ability to generate and validate JSON Web Tokens, as well as links to the main JWT libraries for the most common programming languages.
Essentially, everything you need to start working on adding JWT security into your APIs is already easily accessible through their website.
As a final warning though, I must mention that although the mechanics I covered here are quite straightforward and accessible to everyone, you should understand that only adding JWT security into your API is not going to be enough. You won’t be bulletproof if you just do the above, many smart hackers will find ways around it. There are many other exploits that can (and will) still hurt you, security is about covering all your fronts, not just implementing one generic security scheme. An extra layer of protection that always goes hand in hand with JWT is to secure all your network traffic with an HTTPS connection. In other words, make sure everything that the user sends and receives goes through port 443 (or whatever custom number you might be using) and not for the good old, unsecured port 80.
And that is it! Please feel free to reach out and leave a comment if you feel like I forgot to mention an important aspect of this topic, or if you even found it interesting and helpful, I would love to hear from you as well.
Until the next one!
Plug: LogRocket, a DVR for web apps
LogRocket is a frontend logging tool that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.