Editor’s note: This JWT authentication tutorial was last updated on 12 September 2024 by Chigozie Oduah to discuss modern JWT inefficiencies, such as lack of encryption, reliance on JavaScript for token storage, vulnerabilities like XSS and CSRF attacks, and size constraints imposed by browsers.
In web development, authentication is one of the most complex aspects to implement yourself. Many web applications delegate authentication to third-party authentication services like Auth0 or rely on authentication built into the frameworks or tools they are built with.
This also means that many developers (maybe you too 🙂 ) don’t know how to build at least moderately secure authentication into their web applications. JWT provides an easy way to to do this
With knowledge of some of the security concerns to consider when using JWT, you can implement a more secure authentication as you see with third-party authentication services. So, in this guide, we’ll begin by covering what JWTs are, then we’ll go into how they’re used and why, and finally, we’ll go into the issues and concerns to look out for when using JWTs.
JSON Web Token (JWT) is a standard for structuring data to be transmitted between two parties (commonly server and client). A JWT is a single string made up of two components, a JSON Object Signing and Encryption (JOSE) header and its claims (or payload), both base64url encoded and separated by a period (.
).
This is the structure of a token:
(Header).(Payload)
Here’s an example of a token:
eyJhbGciOiJub25l4oCdfQ.ewogICJpZCI6ICIxMjM0NTY3ODkwIiwKICAibmFtZSI6ICJKb2huIERvZSIsCiAgImFnZSI6IDM2Cn0K
This token is constructed with these two components:
{ "alg": "none" } // base64url encoded to: eyJhbGciOiJub25l4oCdfQ
{ "id": "1234567890", "name": "John Doe", "age": 36 } // base64url encoded to: ewogICJpZCI6ICIxMjM0NTY3ODkwIiwKICAibmFtZSI6ICJKb2huIERvZSIsCiAgImFnZSI6IDM2Cn0
The JOSE header contains details about the type of encryption, signing, or both applied to the token. "alg": "none”
specifies that the token isn’t encrypted or signed.
Claims are the information that JWTs carry. In the context of user authentication and authorization, you can think of it as claims about a user. The claims in this token are made up of three fields id
, name
, and age
.
JSON Web Tokens aren’t sent directly as JSON strings because they’re UTF-8 encoded. This means that they can contain characters that aren’t URL-safe (characters like “/” or “&” for example). They can’t be put safely in HTTP Authorization headers and URI query parameters.
To make tokens URL-safe, they’re encoded into base64url format. This allows them to be safely put in query parameters and authorization headers.
However, this form of JSON Web Tokens is unsecured because there’s no way of ensuring the integrity of its claims, making it very unsafe to use in user authentication.
The type of JWTs used in handling user authentications are signed tokens (or JSON Web Signatures, JWSs). Signed tokens are essentially JWTs with a cryptographically generated signature, to ensure that the claims in the tokens haven’t been tampered with.
Three components go into making Signed tokens:
This is the structure of a signed token:
(Header).(Payload).(Signature)
And here’s an example of a Signed:
ewogICJ0eXAiOiJKV1QiLAogICJhbGciOiJIUzI1NuKAnQp9.ewogICJpZCI6ICIxMjM0NTY3ODkwIiwKICAibmFtZSI6ICJKb2huIERvZSIsCiAgImFnZSI6IDM2Cn0K.f5zWM0sR0rCxPFEbLcid2DdD2rnQ3PkRtEy2rVkRXF0
The token is constructed from these components:
{ "typ":"JWT", "alg":"HS256" } // base64url encoded to: ewogICJ0eXAiOiJKV1QiLAogICJhbGciOiJIUzI1NuKAnQp9
{ "id": "1234567890", "name": "John Doe", "age": 36 } // base64url encoded to: ewogICJpZCI6ICIxMjM0NTY3ODkwIiwKICAibmFtZSI6ICJKb2huIERvZSIsCiAgImFnZSI6IDM2Cn0K
ewogICJ0eXAiOiJKV1QiLAogICJhbGciOiJIUzI1NuKAnQp9.ewogICJpZCI6ICIxMjM0NTY3ODkwIiwKICAibmFtZSI6ICJKb2huIERvZSIsCiAgImFnZSI6IDM2Cn0K
((Header).(Payload)
), with a secret key value your-256-bit-secret
.So how are signed tokens used in authentication? Here’s a simplified outline of the process:
Here’s a visual representation of the process:
Now that you know how to use JWTs in authentication, the only question left is “Why?” Let’s look into that in the next section.
It turns out that authentication isn’t easy to implement securely. I’ve made many web projects with simple hand-written authentication processes, where I just store the user’s identifier and password as plain JSON strings in JavaScript localStorage and pass them to any region of my application that needs authenticated access.
Fortunately, those projects didn’t have many users (or any in most cases), so it wasn’t rewarding to exploit. If the web applications had many (and important) users and had authentication implemented this way, it would’ve meant disaster.
Signed tokens prevent these kinds of disasters by:
But JWTs aren’t perfect solutions for secure authentication. They still have issues and concerns to look out for (and possibly work around) when using them in your project.
JWTs like many other tools in the world, aren’t perfect. They’re good for user authentication, but not without shortcomings. In this section, I’ll address some popular concerns.
So let’s start with the first concern.
Signed tokens provide the benefit of verifying the integrity of the claims in the tokens. This allows them to be useful for authentication purposes. This doesn’t mean that the claims stored in the tokens aren’t hidden.
If your web application needs to store sensitive information in tokens, the website needs to handle them with caution. Generally, you should avoid storing sensitive information in tokens because it is very difficult to protect them against all possible cybersecurity attacks.
In cases where a web application needs to store sensitive information in tokens, encrypted forms of JWTs exist for this reason.
Compared to the internet of the early 2000s modern-day internet is more secure. But, on its own, the modern-day internet still isn’t a hundred percent secure. Anything that JavaScript has access to can still potentially be exploited.
Because of the structure of modern applications, it has become more important for JavaScript to have access to the tokens to, for example, send requests to APIs. However, web applications have reasons for their structure, and in some cases, JavaScript having access to the tokens is unavoidable. Fortunately, the internet has gotten secure enough for access to JavaScript to be less of a concern than it was in the earlier internet.
There isn’t a good solution to this concern. Regardless of where you store tokens, you’re opening the tokens to at least one form of exploit. Storing in cookies or sessions is open to CSRF (Cross-Site Request Forgery) attacks, and storing anywhere JavaScript can access is open to XSS (Cross-Site Scripting) attacks.
Depending on how you store and transmit JWTs, they’re subject to size constraints imposed by browsers. For example, all browsers impose a 4 KB and 5 MB limit on the total amount of data that a web application can store in cookies and JavaScript localStorage respectively.
If your web application uses significant portions of these storage mechanisms (although unlikely), you can use session tokens instead. They’re smaller, but they can’t have payloads, with extra pieces of information, like with JWTs.
JWTs are useful tools in user authorization and authentication, but they’re just standards. They’re not built directly into programming languages or many frameworks. Using them in many cases is based on how you (or the library you choose to generate and handle them) implement JWTs. If you want to learn how to implement them, you can check out our guide on implementing JWT authentication with Vue and Node.js.
Install LogRocket via npm or script tag. LogRocket.init()
must be called client-side, not
server-side
$ npm i --save logrocket // Code: import LogRocket from 'logrocket'; LogRocket.init('app/id');
// Add to your HTML: <script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script> <script>window.LogRocket && window.LogRocket.init('app/id');</script>
Would you be interested in joining LogRocket's developer community?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowConsider using a React form library to mitigate the challenges of building and managing forms and surveys.
In this article, you’ll learn how to set up Hoppscotch and which APIs to test it with. Then we’ll discuss alternatives: OpenAPI DevTools and Postman.
Learn to migrate from react-native-camera to VisionCamera, manage permissions, optimize performance, and implement advanced features.
SOLID principles help us keep code flexible. In this article, we’ll examine all of those principles and their implementation using JavaScript.
21 Replies to "JWT authentication: Best practices and when to use it"
I don’t understand some of the claims here.
“Don’t store it in local storage (or session storage). If any of the third-party scripts you include in your page gets compromised, it can access all your users’ tokens.”
Why does this matter, when you protect against CSRF with CSRF tokens?
I tried storing it in cookie httpOnly but my problem is I cannot pass as request authorization header when making a request to the backend. How will this be solved?
You don’t. Cookies are send with every request you make to the server, so you read from the cookie in the backend instead of the authorization header.
I am thinking to store in authorization the id from db that contains the token, in authorization header the id will be used, or just encrypt all tokens with your master password then add in header, then decrypt at some point :D, really nothing seems safe
How to send cookie while making api call. I tried to add cookie in the header but no use
While testing, superagent makes it easy to set token in cookies. I guess any http agent will help too
did you solve the problem coz i also had the same problem
After some research, yes. It’s automatically passed into the request cookies. Before I use req.headers.authorization in my middleware, now I have to use `req.cookies[‘name’]`. The idea of setting cookie as httpOnly is that you can never call it using JS to alter like localstorage.
×´ there is always the possibility that it’s intercepted and the data deciphered×´ – deciphered is not the right word here since JWT are serialised, not encrypted
“the possibility that it’s intercepted and the data deciphered, exposing your user’s data.”
We only store enough information to identify the user in the jwt token. It can be the user’s id, email, or even another access token (in case you want to implement remote logout or similar features). We don’t store sensitive data (e.g. password,…) in the token, so this should not be an issue.
“Using JWT to authorize operations across servers” do you have any examples for this?
As http is stateless, every request made is new to server, to solve this or remember user/request, people use sessions, where server sends session id, like php sends PHPSESSID(key of cookie) stored at client side in cookie. When user makes another request php server calculate that it’s not new user. Now what if your server redirects you to the another physical server let’s say from example.net to cdn.example.org having different task assigned to them. This can cause problem because only one of the server has the power/logic to decipher that sessid right? Now that can be solved with jwt since you need only need to copy secret_key or simply .env file. And you can still verify and compare passwords
I am new to JWT.
If not through JWT how should we send sensitive data (like a password) to a server while logging in.
Actually it is. If the backend gets id=1 as part of the JWT payload, then it will assume the request is made from the correctly authenticated user with id=1, and thus will complete any request made.
Sending a password (either for logging in, or for creating an account) has nothing to do with JWT. JWT is about authenticating to the server after you have already sent the password. To do this correctly you must only connect via HTTPS.
Hi there, nice article.
I still have a question: if JWT is stored in cookies (secured & httpOnly), then the application is vulnerable to CSRF attacks, am I right?
And if the JWT is stored elsewhere accessible from JS then, the app is vulnerable to XSS.
What is the best solution?
Good article. Thank you
Great article. Really helped me figure out my backend authentication strategy, thanks again LogRocket!
Recommendation: replace the terms “blacklist” and “whitelist” with “blockedlist” and “allowedlist”. I know they’re traditional terms but the racial undertones are not friendly and could be done away with 🙂
There are so many issues with this article. Let’s start with the basic out of the gate. JWT is a token format. There are two common implementation uses of JWT, JWS and JWE. JWS is a signed token, JWE is an encrypted token. Use the correct JWS/JWE for what you are trying to protect.
Next, JWT is a text string, this can be embedded in email as part of a link to not expose information, it can be placed in cookies, it can be placed HTTP headers…. How JWT is used has nothing to do with the specification.
Next consider the fact that OAuth utilizes JWT, and this is the foundational protocol for the majority of single sign on (SSO) applications out there. If JWT was this bad, you would think one of these companies would point it out.
If you use cookies to store the authentication data, you are susceptible to CRSF. In fact CSRF is pretty much only possible if you store the authentication data as a cookie. On the flip side, store data in local or session makes it XSS possible.
If you want to ensure your system is secure, you really need to come at the problem from multiple directions. For web applications, using both a cookie and token in the HTTP Header with different values will provide the best protection against XSS and CSRF
Tim
Less secure? Than what?
Storing *anything* in a cookie ? Bad reccomendation (csrf).
Xss ? I dont see the relevance of that in this article whatsoever. Using secure http-only cookies doesn’t help against xss.
is no one having issues with incognito mode? My auth flow won’t work on Chrome’s incognito mode. The cookie were the JWT is stored is completely blocked by chrome. I don’t know what todo. It’s considered third party because I set the cookie from the ‘backend’ which is hosted in a different url than the frontend. this is how i set the cookie res.cookie(‘token’, token, {
httpOnly: true,
secure: true,
sameSite: ‘none’,
partitioned: true, // I added this due to Chrome’s warning that they will block all third party cookies some time soon
});