DEV Community

Vladimir Novick for Hasura

Posted on • Originally published at blog.hasura.io

The Ultimate Guide to handling JWTs on frontend clients (GraphQL)

This is an excerpt of an original post published on blog.hasura.io

JWTs (JSON Web Token, pronounced 'jot') are becoming a popular way of handling auth. This post aims to demystify what a JWT is, discuss its pros/cons and cover best practices in implementing JWT on the client-side, keeping security in mind. We’ve kept the examples especially relevant to GraphQL clients.

Introduction: What is a JWT?

For a detailed, technical description of JWTs refer to this article.

For the purposes of auth, a JWT is a token that is issued by the server. The token has a JSON payload that contains information specific to the user. This token can be used by clients when talking to APIs (by sending it along as an HTTP header) so that the APIs can identify the user represented by the token, and take user specific action.

But can’t a client just create a random JSON payload an impersonate a user?

Good question! That’s why a JWT also contains a signature. This signature is created by the server that issued the token (let’s say your login endpoint) and any other server that receives this token can independently verify the signature to ensure that the JSON payload was not tampered with, and has information that was issued by a legitimate source.

But if I have a valid and signed JWT and someone steals it from the client, can’t they use my JWT forever?

Yes! If a JWT is stolen, then the thief can can keep using the JWT. An API that accepts JWTs does an independent verification without depending on the JWT source so the API server has no way of knowing if this was a stolen token! This is why JWTs have an expiry value. And these values are kept short. Common practice is to keep it around 15 minutes, so that any leaked JWTs will cease to be valid fairly quickly. But also, make sure that JWTs don’t get leaked.

That’s why it’s also really important not to store JWT on the client, say via cookies or localstorage. Doing so you make your app vulnerable to CSRF & XSS attacks, by malicious forms or scripts to use or steal your token lying around in cookies or localstorage.

So does a JWT have a specific kind of structure? What does it look like?

A JWT looks something like this, when it's serialized:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.XbPfbIHMI6arZ3Y922BhjWgQzWXcXNrz0ogtVhfEd2o

If you decode that base64, you'll get JSON in 3 important parts: header, payload and signature.

The serialized form is in the following format:

[ base64UrlEncode(header) ] . [ base64UrlEncode(payload) ] . [signature ]

A JWT is not encrypted. It is based64 encoded and signed. So anyone can decode the token and use its data. A JWT's signature is used to verify that it is in fact from a legitimate source.

Here is the diagram of how a JWT is issued(/login) and then used to make an API call to another service(/api) in a nutshell:

Ugh! This seems complicated. Why shouldn’t I stick to good old session tokens?

This is a painful discussion on the Internet. Our short (and opinionated answer) is that backend developers like using JWTs because a) microservices b) not needing a centralized token database.

In a microservices setup, each microservice can independently verify that a token received from a client is valid. The microservice can further decode the token and extract relevant information without needing to have access to a centralized token database.

This is why API developers like JWTs, and we (on the client-side) need to figure out how to use it. However, if you can get away with a session token issued by your favorite monolithic framework, you’re totally good to go and probably don’t need JWTs!

Continue reading here

Top comments (8)

Collapse
 
lvanderree profile image
Leon van der Ree • Edited

You say no to store your accesstokens on the client, in cookies or localstorage. But I wonder:

  1. Of course you have to store your JWTs somewhere (memory), to be able to provide them to your (http/rest)clients which will do the request to your APIs. And if store the in memory, aren't you still vulnerable for (targetted) XSS-attacks, or how do you protected yourself from these attacks.

  2. Even if you can protect your memory from attacks, how do you get the accesstoken in memory? You probably have some credentials in memory as well (refreshtoken) so you don't have to ask the use to relogin every 15minutes, with which you can authenticate on the authentication server to get the accesstoken. How can you prevent a (targetted) XSS-attack from not doing the same to acquire the accesstoken?

I can imagine that using sessions (or maybe tokens) in SECURE HTTP-ONLY STRICT/LAX cookies is a safer solution. If you've got (micro-)services that you want to run without all of them having to query for the session, why not put a service in front of them, that does the translation from session to JWT for calls coming from the internet. Something you probably have to do with JWTs as well, if you want to check if the valid JWT hasn't been revoked, for example when the user logged out, while the JWT hasn't been expired yet.

Collapse
 
vladimirnovick profile image
Vladimir Novick
  1. You store them in memory, but not on the global scope. So they can be accessed only from your code or if an attacker reverse engineer your code. So you should never expose memory token getter function to the outside world. That's how you can prevent attacker from getting the token.

  2. In memory, I have a getter function to get the token and every 15 minutes I do refresh_token call (silent refresh). All of this is written in the blog post in Silent refresh section

If you need to validate if your tokens were revoked or not that can be done by blacklisting them which is also described in the blog post in blacklisting tokens section. Session-based authentication is problematic in a microservices architecture. I referred to that also in blog post

Collapse
 
lvanderree profile image
Leon van der Ree • Edited

Hello Vladimir, thanks for your fast reply and further info.

I am looking for a way to use JWTs in a safe way, and this blog provided a good base to get more information on how to achieve this.

I read your links, but am still not convinced that JWTs (without binding) can be protected from XSS theft and misuse.

  1. I understand you should not store tokens in global scope, but somewhere in your JS-application you have a function to get the accesstoken. This function can store the token in some private property of an object, but:

    • via XSS I can create a cloned version of this version that does the same thing (see 2), but opens access to the acquired token to the hacker
    • and eventually the token should leave the private property to provide the token to (a http-client to) inject it in E.G. an authorization header. With XSS you can also inject a service that gets the accesstoken via the token-object. I am aware that this can only be done with knowledge about the working of the code, that's why I called it a targeted attack, but when the accesstoken provides access to valuable resources, this will be done.
  2. the (silent) refresh function you describe still looks vulnerable to XSS to me. You protect the refresh-token from getting stolen, but a malicious-script can call the /refresh_token API via the users browser, the cookie gets automatically applied, and now the script has an accesstoken it can publish to the hacker.

Of course CORS can be used to minimize the possibilities to inject XSS-scripts and to publish data to the outside world, but with analytic services, advertisement networks, and social-media integrations this still is very hard to prevent completely.

The best option I have seen is Token Binding but this isn't supported in browsers. You can try to implement something similar yourself with the JS-crypto functions, but then access to your API will be much harder to implement.
The safest way to provide access to your services is then by using session-cookies (although API calls can still be compromised in the same way with targeted XSS attacks; calling API's via the users browser that will automatically applies cookies, you can at least not access the API's from other systems after a token is stolen).

Thread Thread
 
vladimirnovick profile image
Vladimir Novick

I am not saying you are not 100% vulnerable, but you are way less vulnerable than in other solutions and without having a centralized token database.

As for cookie automatically applied when sent to malicious script, your cookie is http-only so it will be sent automatically only to the same domain thus reducing the risk of getting stolen. Also, it cannot be accessed through JS.

Collapse
 
tkdaj profile image
tkdaj

If you only store your JWTs in memory, how do you deal with a user refreshing the browser? In my application I would like a user to be able to refresh the browser and automatically be logged back in.

Thread Thread
 
vladimirnovick profile image
Vladimir Novick

I covered that later in blog post. Short answer - refresh tokens and silent refresh. Please check the full blog post here: blog.hasura.io/best-practices-of-u...

Collapse
 
dance2die profile image
Sung M. Kim

Hi Vladimir.

This looks like a good post here. You think you might be able to share this in full on DEV?

DEV generally asks that folks share their posts in full if possible and there is tooling provided (dev.to/settings/publishing-from-rss) to make it so that it's relatively easy to repost from outside blogs.

And thank you for Hasura as I found Hasura pretty cool in terms of architecture and how it fits well in the front-end community.

Collapse
 
xiaoyutamu profile image
Xiaoyu Li

What if client refresh page before new token send back? client logged out automatically since old token already deleted.