This is one of the short articles that should help you quickly set upĀ basic form of authentication with JWT. I’m guessing that you already know what JWT is. Writing custom authentication flow can be a pain in the butt, butĀ JWT makes a bit easier by introducing a secure communication channel between browser and server usingĀ access and refresh tokens.
Although JWT is a nice platform,Ā you should never rely just on JWTĀ when it comes to authentication and/or authorization. In this tutorial, I will be covering a very simple case where weĀ generate access and refresh tokens for the user and return them to the browser as aĀ httpOnly cookie. (I’m assuming that you already have your basic authentication with database set up)Ā We will store the data inĀ Redis which is very easy to install if you have docker on your machine.Ā Let’s get right into it.
1. Install Redis using Docker
Redis is an in-memory (can be also persisted) key/value store, which we will use for storing user tokens. The easiest way to install Redis is using a Docker installation. By using Docker, you don’t interfere with your operating system at all. Instead, your Redis keystore will run in a separate container which will be only used by your web app.
To install Docker, run:
sudo apt install apt-transport-https ca-certificates curl software-properties-common curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add - sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu bionic stable" sudo apt update sudo apt install docker-ce
Then, you can use Docker to fire up your Redis container.
docker run --name myrediskeystore -d redis:latest
Check the status of your container and if it isn’t running, start it up.
vlm@vlm:~$ docker ps -a CONTAINER ID STATUS PORTS NAMES ea44f1638357 Up 9 seconds 6379/tcp myrediskeystore # if your container isn't up, run "docker start <container id>: vlm@vlm:~$ docker start ea44f1638357
If you’d like to start your RedisĀ with a persistent storageĀ head over here, or you’d like to know more about the docker image, go here.
2. Install JWT and Redis dependencies
In your project/web app, run following two lines to install dependencies which we will use for this tutorial.
npm install jsonwebtoken --save npm install redis --save
It’s also a good idea to read documentation, so you have an overview of what we will be doing. Head over to the JWT repoĀ and the Redis repo now.
3. Import dependencies and connect to Redis
Copy and paste this code to your main application file (index.js or so).
const redis = require("redis"); const jwt = require('jsonwebtoken'); var rediscl = redis.createClient(); rediscl.on("connect", function () { console.log("Redis plugged in."); });
As you noticed, I’m not passing any configuration to Redis. createClient()Ā will use default values if no configuration is specified. And because I’m running Redis on localhost, on default port and without basic authentication, I could leave the “constructor” empty. For the purposes of this tutorial it’s okay to have the connection unprotected, but if you ever decide to take it further, you should secure your installation.
4. Define JWT variables
Paste following code below the code from step 3.
const jwt_secret = "jwtfanhere"; const jwt_expiration = 60 * 10; const jwt_refresh_expiration = 60 * 60 * 24 * 30;
- jwt_secretĀ is a keyword or sentence that will be used on your server to encrypt the payload
- jwt_expirationĀ is time during which the access token will be valid
- jwt_refresh_expirationĀ is time during which the refresh token will be valid
Usually, refresh tokens can stay the same for a longer period of time, maybe even a year or two (wow, that was optimistic). Access tokens are rotated all the time, in short periods of time, because if someone hacks you and is now in possession of your access token, you probably don’t want him to hang around on your web app profile for too long. In fact, you want it to be as short as possible. That’s why we set the access token expiration to 10 minutes.
5. Define application routes
In this tutorial, I’m using Express as an application server, but you can go ahead and use any other framework. Be careful though, cookies might be located in different part of req/res if you use other framework, so some refactoring might be needed.
There will be four routes in our small web app.
app.post("/register", (req, res, next) => { // When user registers, you don't really do anything about it // in your JWT logic. You will first give them tokens when // they log in. In this part of the code, you store // the user somewhere into database and maybe send verification // link on email } app.post("/login", (req, res, next) => { // Loging the user in - in this part, we will generate a new // access-refresh token pair and return it to the user as part // of the response object, in httpOnly cookies. We will also save // this pair in Redis. } app.post("/logout", (req, res, next) => { // Logging the user out - we remove user's tokens from Redis // as well as from the httpOnly cookies } app.post("/profile", (req, res, next) => { // Here we can check if cookies are present and valid. // Then we use JWT payload to determine user's ID. }
6. Generate or delete user tokens on login/logout
This part is easy – it simply deals with issuing new tokens on user login, or removal of tokens on user logout.
As soon as the user has tokens, we can start validating them. This will be the next step; for now, use the code below in your application routes.
app.post("/login", (req, res, next) => { // When user logs in, there is no token pair in the browser // cookies. We need to issue both of them. Because you also // log user in in this step, I assume that you already have // their user ID. let user_id = 2212; // Generate new refresh token and it's expiration let refresh_token = generate_refresh_token(64); let refresh_token_maxage = new Date() + jwt_refresh_expiration; // Generate new access token let token = jwt.sign({ uid: user_id }, jwt_secret, { expiresIn: jwt_expiration }); // Set browser httpOnly cookies res.cookie("access_token", token, { // secure: true, httpOnly: true }); res.cookie("refresh_token", refresh_token, { // secure: true, httpOnly: true }); // And store the user in Redis under key 2212 redis.set(user_id, JSON.stringify({ refresh_token: refresh_token, expires: refresh_token_maxage }), redis.print ); } app.post("/logout", (req, res, next) => { // Delete user refresh token from Redis redis.del(req.body.uid); // ... and then remove httpOnly cookies from browser res.clearCookie("access_token"); res.clearCookie("refresh_token"); res.redirect("/"); }
7. Verify user when accessing sensitive routes
This is the most important – and most difficult – part of the process. So right now, users either have or have not the token pair. If they do, it means that they logged in successfully and are a valid user of your web app. If they don’t, they probably registered, but never logged in (or manually deleted these tokens from their browser). And if they do have them but tokens are invalid, they either expired, or someone is trying to hack your web app (with invalid tokens).
Whenever someone accesses sensitive data on your web, or tries to trigger a function which inserts/updates/deletes data to/from database, you need to make sure that the user is valid. The following function does just that.
But there is still one scenario which you should try to prevent yourself. If you get hacked and someone gets your tokens, the hacker can now try to access every part of your web app. In my tutorial, we take care of authorization (authorization, not authentication) inside of JWT payload body, which is only secure on basic level. Try to think about ways to improve authorization in my code and let me know in the comment section if you think of something š
// Let's define a helper function that we will use in most of our routes. // If you can't see the code properly, click on "Open Code in new Window" function validate_jwt(req, res) { // Let's make this Promise-based return new Promise((resolve, reject) => { let accesstoken = req.cookies.access_token || null; let refreshtoken = req.cookies.refresh_token || null; // Check if tokens found in cookies if (accesstoken && refreshtoken) { // They are, so let's verify the access token jwt.verify(accesstoken, jwt_secret, async function(err, decoded) { if (err) { // There are three types of errors, but we actually only care // about this one, because it says that the access token // expired and we need to issue a new one using refresh token if (err.name === "TokenExpiredError") { // Let's see if we can find token in Redis. We should, because // token expired, which means that we already inserted it into // redis at least once. let redis_token = rediscl.get(decoded.uid, function(err, val) { return err ? null : val ? val : null; }); // If the token wasn't found, or the browser has sent us a refresh // token that was different than the one in DB last time, then ... if ( !redis_token || redis_token.refresh_token === refreshtoken ) { // ... we are probably dealing with hack attempt, because either // there is no refresh token with that value, or the refresh token // from request and storage do not equal for that specific user reject("Nice try ;-)"); } else { // It can also happen that the refresh token expires; in that case // we need to issue both tokens at the same time if (redis_token.expires > new Date()) { // refresh token expired, we issue refresh token as well let refresh_token = generate_refresh_token(64); // Then we assign this token into httpOnly cookie using response // object. I disabled the secure option - if you're running on // localhost, keep it disabled, otherwise uncomment it if your // web app uses HTTPS protocol res.cookie("__refresh_token", refresh_token, { // secure: true, httpOnly: true }); // Then we refresh the expiration for refresh token. 1 month from now let refresh_token_maxage = new Date() + jwt_refresh_expiration; // And then we save it in Redis rediscl.set( decoded.uid, JSON.stringify({ refresh_token: refresh_token, expires: refresh_token_maxage }), rediscl.print ); } // Then we issue access token. Notice that we save user ID // inside the JWT payload let token = jwt.sign({ uid: decoded.uid }, jwt_secret, { expiresIn: jwt_expiration }); // Again, let's assign this token into httpOnly cookie. res.cookie("__access_token", token, { // secure: true, httpOnly: true }); // And then return the modified request and response objects, // so we can work with them later resolve({ res: res, req: req }); } } else { // If any error other than "TokenExpiredError" occurs, it means // that either token is invalid, or in wrong format, or ... reject(err); } } else { // There was no error with validation, access token is valid // and none of the tokens expired resolve({ res: res, req: req }); } }); } else { // Well, no tokens. Someone is trying to access // your web app without being logged in. reject("Token missing.") }; }); } // A little helper function for generation of refresh tokens function refresh_token(len) { var text = ""; var charset = "abcdefghijklmnopqrstuvwxyz0123456789"; for (var i = 0; i < len; i++) text += charset.charAt(Math.floor(Math.random() * charset.length)); return text; }
Hopefully you didn’t lose your mind over the amount of code. I tried to explain as much as I could in the code comments. But that’s almost all. Right now we can:
- Push tokens to user browser and to our Redis storage upon login
- Withdraw tokens from user browser and our Redis storage upon logout
- Use the code from above to:
- Verify user by checking if he has tokens
- Verify these tokens based on our server secret
- When tokens expire, we check if user with that specific ID (from JWT payload) sent us the same refresh token as the one in DB. If yes, we generate new token pair. If not, it’s possible that someone tries to mimic user ID in JWT payload but actually has different expired tokens.
There is still space for improvement, although the solution it’s pretty solid! The only thing left for you to do is to make sure that user with the specific ID can only insert/update/delete resources that are his own.
So now we use this code in a following way:
app.post("/profile", (req, res, next) => { // let's say that the unknown user wants to edit some profile validate_jwt(req, res, pgdb).then(result => { // Pass your modified request and result objects further // to any method that generates content, or works with DB, // or whatever you like some_other_method(result.req, result.res); }) .catch(error => { throw error; }); }
You can of course use this code on any route in your web app.
Conclusion
Let me know if this helped you in the comment section below. I had very limited time to write this article, so I will be more than happy to hear from you if you have some feedback. Thanks.
I think this post makes sense and really helps me, so far I’m still confused, after reading the posts on this website I understand.
Iām often to blogging and i really appreciate your content. The article has actually peaks my interest. Iām going to bookmark your web site and maintain checking for brand spanking new information.
Iām often to blogging and i really appreciate your content. The article has actually peaks my interest. Iām going to bookmark your web site and maintain checking for brand spanking new information.
Awesome! Its genuinely remarkable post, I have got much clear idea regarding from this post
You have noted very interesting points! ps decent web site. āFormal education will make you a living self-education will make you a fortune.ā by Jim Rohn.
I like the efforts you have put in this, regards for all the great content.
Keep working ,terrific job!
For the reason that the admin of this site is working, no uncertainty very quickly it will be renowned, due to its quality contents.
Cool that really helps, thank you.
naturally like your web site however you need to take a look at the spelling on several of your posts. A number of them are rife with spelling problems and I find it very bothersome to tell the truth on the other hand I will surely come again again.
I think the content you share is interesting, but for me there is still something missing, because the things discussed above are not important to talk about today.
Awesome! Its genuinely remarkable post, I have got much clear idea regarding from this post.
Great information shared.. really enjoyed reading this post thank you author for sharing this post .. appreciated
I appreciate you sharing this blog post. Thanks Again. Cool.
I appreciate you sharing this blog post. Thanks Again. Cool.
Cool that really helps, thank you.
I am truly thankful to the owner of this web site who has shared this fantastic piece of writing at at this place.
This is really interesting, Youāre a very skilled blogger. Iāve joined your feed and look forward to seeking more of your magnificent post. Also, Iāve shared your site in my social networks!
Good post! We will be linking to this particularly great post on our site. Keep up the great writing
Cool that really helps, thank you.
very informative articles or reviews at this time.
Great wordpress blog here.. Itās hard to find quality writing like yours these days. I really appreciate people like you! take care
Cool that really helps, thank you.
There is definately a lot to find out about this subject. I like all the points you made
Dead indited articles, Really enjoyed looking through.
I am truly thankful to the owner of this web site who has shared this fantastic piece of writing at at this place.
This is my first time pay a quick visit at here and i am really happy to read everthing at one place
I need to to thank you for this fantastic read!! I definitely loved every bit of it. I have you bookmarked to check out new stuff you post?
Good post! We will be linking to this particularly great post on our site. Keep up the great writing
Great information shared.. really enjoyed reading this post thank you author for sharing this post .. appreciated
Awesome! Its genuinely remarkable post, I have got much clear idea regarding from this post
Don’t forget to watch videos on the YouTube channel Bambu4d
Nice post, But don’t forget to visit my website Bambu4d
Cool that really helps, thank you.
Itās really a nice and useful piece of info. Iām glad that you shared this useful info with us. Please keep us informed like this. Thanks for sharing.
I just like the helpful information you provide in your articles
I need to to thank you for this fantastic read!! I definitely loved every bit of it. I have you bookmarked to check out new stuff you post?
This is really interesting, Youāre a very skilled blogger. Iāve joined your feed and look forward to seeking more of your magnificent post. Also, Iāve shared your site in my social networks!
very informative articles or reviews at this time.
Cool that really helps, thank you.
Youāre so awesome! I donāt believe I have read a single thing like that before. So great to find someone with some original thoughts on this topic. Really.. thank you for starting this up. This website is something that is needed on the internet, someone with a little originality!
naturally like your web site however you need to take a look at the spelling on several of your posts. A number of them are rife with spelling problems and I find it very bothersome to tell the truth on the other hand I will surely come again again.
This is my first time pay a quick visit at here and i am really happy to read everthing at one place
naturally like your web site however you need to take a look at the spelling on several of your posts. A number of them are rife with spelling problems and I find it very bothersome to tell the truth on the other hand I will surely come again again.
Iām often to blogging and i really appreciate your content. The article has actually peaks my interest. Iām going to bookmark your web site and maintain checking for brand spanking new information.
Thanks for a marvelous posting! I genuinely enjoyed reading it, you are a great author.I will always bookmark your blog and will often come back down the road. I want to encourage continue your great job, have a nice weekend!
This is my first time pay a quick visit at here and i am really happy to read everthing at one place
Itās really a nice and useful piece of info. Iām glad that you shared this useful info with us. Please keep us informed like this. Thanks for sharing.
Great wordpress blog here.. Itās hard to find quality writing like yours these days. I really appreciate people like you! take care
actually awesome in support of me.
That’s good, but I still don’t understand the purpose of this page posting, no or what and where do they get material like this.
https://duhocphap.edu.vn/?wptouch_switch=desktop&redirect=https://bambu4d.com
Good post! We will be linking to this particularly great post on our site. Keep up the great writing
I am truly thankful to the owner of this web site who has shared this fantastic piece of writing at at this place.
Awesome! Its genuinely remarkable post, I have got much clear idea regarding from this post
Very well presented. Every quote was awesome and thanks for sharing the content. Keep sharing and keep motivating others.
I very delighted to find this internet site on bing, just what I was searching for as well saved to fav
very informative articles or reviews at this time.
Awesome! Its genuinely remarkable post, I have got much clear idea regarding from this post
Great information shared.. really enjoyed reading this post thank you author for sharing this post .. appreciated
naturally like your web site however you need to take a look at the spelling on several of your posts. A number of them are rife with spelling problems and I find it very bothersome to tell the truth on the other hand I will surely come again again.
I do not even understand how I ended up here, but I assumed this publish used to be great
Great information shared.. really enjoyed reading this post thank you author for sharing this post .. appreciated
I think the content you share is interesting, but for me there is still something missing, because the things discussed above are not important to talk about today.
Awesome! Its genuinely remarkable post, I have got much clear idea regarding from this post
Very well presented. Every quote was awesome and thanks for sharing the content. Keep sharing and keep motivating others.
naturally like your web site however you need to take a look at the spelling on several of your posts. A number of them are rife with spelling problems and I find it very bothersome to tell the truth on the other hand I will surely come again again.
Very well presented. Every quote was awesome and thanks for sharing the content. Keep sharing and keep motivating others.
I like the efforts you have put in this, regards for all the great content.
I think this post makes sense and really helps me, so far I’m still confused, after reading the posts on this website I understand.
This was beautiful Admin. Thank you for your reflections.
Good post! We will be linking to this particularly great post on our site. Keep up the great writing
Iām often to blogging and i really appreciate your content. The article has actually peaks my interest. Iām going to bookmark your web site and maintain checking for brand spanking new information.
Great information shared.. really enjoyed reading this post thank you author for sharing this post .. appreciated
very informative articles or reviews at this time.
That’s good, but I still don’t understand the purpose of this page posting, no or what and where do they get material like this.
Cool that really helps, thank you.
Hi there to all, for the reason that I am genuinely keen of reading this websiteās post to be updated on a regular basis. It carries pleasant stuff.
Nice post. I learn something totally new and challenging on websites
I appreciate you sharing this blog post. Thanks Again. Cool.
Great website. Lots of useful information here. I look forward to the continuation. evden eve taÅıma firmaları
Good post! We will be linking to this particularly great post on our site. Keep up the great writing siam sport
Youāre so awesome! I donāt believe I have read a single thing like that before. So great to find someone with some original thoughts on this topic. Really.. thank you for starting this up. This website is something that is needed on the internet, someone with a little originality!
I just like the helpful information you provide in your articles
Cool that really helps, thank you.
This is really interesting, Youāre a very skilled blogger. Iāve joined your feed and look forward to seeking more of your magnificent post. Also, Iāve shared your site in my social networks!
Awesome! Its genuinely remarkable post, I have got much clear idea regarding from this post.
I just like the helpful information you provide in your articles
I do not even understand how I ended up here, but I assumed this publish used to be great
Try to slowly read the articles on this website, don’t just comment, I think the posts on this page are very helpful, because I understand the intent of the author of this article.
very informative articles or reviews at this time.
Pretty! This has been a really wonderful post. Many thanks for providing these details.
Thank you for great content. I look forward to the continuation. crifree