Managing Authentication in Next.js


Next.js is a compelling framework that makes many tasks effortless providing many out-of-the-box solutions. But when it comes to authentication and user security, it's our mission to make it reliable, secure, and efficient. In this workshop, we'll focus on different user authentication and session management approaches, starting from a custom authentication strategy (that we will build together), and ending learning how to identify and integrate the right auth provider (Auth0, Firebase, etc.) for any app.

Table of contents:

- A brief introduction to Next.js

- Building an authentication mechanism from scratch

- Why we should avoid custom authentication

- How to identify the proper authentication mechanism and provider

- Integrating NextAuth.js, Auth0, Firebase, or any other provider


So, welcome everybody. This is my first workshop at Node Congress. I'm incredibly excited to be here. And before we start, I'd like to introduce myself. First of all, I have two screens. So, if some of you have to ask me something, you see I'm not looking at the camera. It's because I have a second screen. So, no offense. I'm just writing code on the second screen and looking at you on the other one. So, I want this to be clear. No offense. Really sorry about that. Okay. So, as I was saying, welcome to the workshop, Managing authentication in next.js. Before we start, let me introduce myself. I am Michele Riva. I work as a Senior Software Architect at Nierform. We are the main node.js maintainers, and we have a lot of expertise in node.js, next.js, react, whatever happens in the javascript playground. And we are hiring, just in case. I'm also the author of Real World next.js book, which will be published at the end of this month, and we will get there in just a second. I'm also a Google Developer Expert and a Microsoft MVP. But talking about my book, I prepared a 25% discount code for every one of you willing to buy the hard copy, so the printed one, which is typically more expensive. So, I want a discount. You can use the 25 next.js code at checkout on packet publishing. And please, in case you want to use this discount code and you can't remember where to get it, please just write me in the chat. I will write a reminder on Discord so that you have a couple of weeks to use this discount code. So, what are we going to do today? This is a brief agenda of what we're going to do today. First of all, I want to give you an introduction on authentication, authorization, and how to deal with that in next.js and javascript apps in general. Then we will start looking at some custom authentication methods. And we will be concentrating on the good, the bad, and the ugly of writing custom authentication strategies. Then we will have a break of five minutes, so you can drink a glass of water. You can rest your eyes for a moment after one hour straight on the workshop. Then we will be back again trying to understand how to integrate Firebase, how to zero, or whatever we want. So, the point for this workshop is not to teach you how to implement a specific authentication method. It's about telling you what are the pros and cons of doing this from scratch, adopting a framework, where we typically fail, and all the security concerns about custom authentication. And then if we have time, we will have a Q&A. Please note, this workshop is meant to be three hours long. And as you can see, we only have two portions of 60 minutes, and there's a reason why. That's because 60 minutes, it's me talking. I want to interact with you. So, if you have any question in any moment, please feel free to stop me. Ask me as many questions as you want. And until it's not clear what I'm talking about, we won't proceed, because I want every one of you to be aware of what we're thinking. And, you know, I'm not an active English speaker, so sometimes I can find it difficult to find the right words. I do understand this. So, please, please, please block me if you see that I'm not telling something that you can understand, of course, because it's my fault. So, that said, I feel like we can get started. If you want, you can follow along with me. I've prepared some code, and I'm going to send you the link on Discord. So, if you haven't already, Daria has put a link on the Zoom chat to assist the Discord room. So, every communication, I'd like to send every communication on Discord so that it's easier for us to gather here and share communications and whatever. I will always keep Discord open for the entire workshop. So, if you have questions and you can't use the microphone, don't worry. Just put up a question on Discord. I will keep an eye on it. So, I will give you, like, a couple of minutes to join in Discord. If you haven't already, you can find, again, you can find the link on the Zoom chat. Okay. I see Evelyn asking for the link. So, are you asking for the link for joining Discord or book? Okay, the book. So, yeah, let me send you. Okay. This is the link. And let me send you the discount code. Again, you have a couple of weeks to redeem your discount code. So, feel free to use it and share it with your friends, colleagues. Not a problem at all. Feel free to share with whoever you want. Okay. I can't see. Oh, it's 25% discount. So, I hope this can be helpful for everyone. Okay. So, let me show you the boilerplate code before we start doing anything. So, as I was saying, I've created the 00auth workshop boilerplate, which is a standard next.js app, which is linked to an Hasura graphql endpoint for retrieving data. Don't worry about graphql. Don't worry about Hasura or whatever. It's not important at that stage. I just use it as an easy method for retrieving data, which is quite important at the moment, you know. So, let me start the, oops, let me start the project. Let me move a bit the screen. Okay. One thing to consider, I'm currently live on Zoom, so code compilation could be a bit slower. It's not because of next.js, but because of bandwidth and, of course, computing power, which is reserved to Zoom to currently working. So, this is the boilerplate code I created. And as you can see, it's a kind of Amazon, you know. It looks like Amazon, but it's not really. And we basically have some products. We can enter the products, of course, see how much they cost. We can add them to the card. Oh, but we cannot because we are not authenticated. So, what we want to do is to allow people to add products to the card when they are authenticated. They can log in. And right now, as you can see, nothing happens if I click. Now I'm clicking. We can register. And again, I'm clicking and nothing happens. And that's it. So, it's really simple. I've also created a couple of private routes, which now are public, and we will try to make them private in just a second. So, if I go on profile, as you can see, it doesn't require me to be authenticated right now, which is an issue. And we are going to solve this. But we can go on recent orders. And as you can see, it's dynamically loading recent orders. And we have like, okay, this is what I bought. So, I can go back and see all the products I bought, the date, the order ID, and the order total. I can change the password and look at my profile. And as you can see, this is not my actual profile, but my actual address, but I'm just showing my name and the address. So, all those information are public right now, and that's clearly an issue. So, how do we secure those information? And that's the billion dollar question right now. Let me go. Oh, my God. There's zoom. Okay, I'm sorry. Okay. So, I'm going to use to draw some diagrams for now so that we can understand what we want to do. Please, again, remember, block me whenever you want and ask for more explanation if you need it. If I'm not explaining something well enough, please block me. And I will repeat in other words. This is really important. So, okay, let's see how many routes do we have and how we want to secure them. So, this, let me zoom a bit, maybe a bit less. Okay. So, this will be our application. This is our node.js server. So, let's color it with green, which is the node.js color. So, whatever we have inside that page, it's public at the moment. So, we can try to divide that into two separate pieces, like follow. So, we can do the, say, the violet one will be the public, and the red one will be the private. So, we have some, you know, some routes. We have some pages that are currently public. We want to move them into the private one. Let's see how to do that. So, first of all, we have the home page, which will always remain public. As you may know, of course, we have the single product page. So, product page, which will always remain public. No doubts about that. As you can see, the cart page, we couldn't add anything to the cart because we weren't authenticated. So, cart page is private. Let me write here. Public. Oops. And this is going to be private. So, the cart page, the profile page. So, profile page, recent orders, change password, and the last one will be the account page. So, as you can see, we have one, two, three, four, five, six, seven. We have seven pages, and only two of them will be public at the end of the day. So, we want to make sure that all the private routes will be, of course, private. And how we do that. So, as I was saying during the introduction, we will start by building a custom authentication method from scratch, which is terrible. I want to say that from now on, please keep in mind that writing custom authentication method is not easy. There are tons of companies such as Firebase, or Auth0, or Okta, and aws that are investing a serious amount of money to keep authentication secure and reliable and easy to implement and really hard to hack, which is incredibly important. If there's any one of you that is working in a context where GDPR, so data protection regulation, for European countries, is involved, this is especially important because fines are really high. So, high. So, if you're breaking the GDPR rules, your website cannot land European market. This is incredibly important. Please be aware of that. Okay. So, what are we going to do? So, as you can see, we have just defined a couple of public pages, but it's not enough. We will need some api pages at a certain point. So, let me copy paste this block. Let me make it a bit more appealing because I will be sharing this with you at the end of the workshop. Let's make it yellow. So, whatever we have inside the yellow box will be our api pages, where we will be handling our authentication. So, let me try. I would like this to be a bit of a kind of workshop where I can interact with people. So, let's say that. We want to implement authentication in our application. Where should we start? Can any one of you raise their hand if they know one typical api that we may need for implementing authentication on a website? Please raise your hand on Zoom and I will give you the word. Otherwise, you can also write on the chat if you can use your microphone or on Discord, of course. So, please. Oh, okay. Sorry. I see you're raising your hands. Please tell me how to pronounce your name correctly. I don't want to mispronounce it. And then go ahead. I fear you're on mute. Me? Or we have multiple people raising hands. Okay. No, it's you. It's you. Sorry. Okay. It's Basil. Basil. Okay. Awesome. Awesome. Nice to meet you. Nice to meet you. I would say we need a register route to register. Yeah. That's correct. That's correct. So, let's put the first route we will be implementing. So, the register. Awesome. So, thank you, Basil. You can lower your hand. And I would like to ask Amandib if he has some other idea. What else would we need? So, for api, that would be an authentication api that would serve some credentials and respond whether it's valid or not? Yeah. So, you mean login, right? Yes. Awesome. Yeah, that's correct. So, we will need a login api. What else would we need? So, is there anyone else with any idea? Logout. Sorry? Logout. Logout. Yeah, that's correct. I wasn't thinking of that, but you are absolutely right. Okay. Thank you, Amandib. I would like to ask someone else if there's any idea. Because we don't, I mean, I wish we need only three routes, but it's not like that. So, we need more. Checkout order? Yeah. Checkout order, but it's not about authentication. You're absolutely correct for the context we are building our application in. But I'd like to know if there's something else for, I see Santiago, validate account. Okay, that's correct. So, I put my email, for example, and I want to validate my email. That's absolutely correct. So, I would call it email verification. That's correct. Resetting password. That's also correct. Awesome. Do we miss something? I think we are okay. And get user, of course, refresh token. Awesome. I see people already having experience in that field. That's absolutely awesome. So, you're both correct. We would need many other routes, such as the, let's call it session route. That gives us the current user, just like Pavel was saying. We will need the refresh token, just in case we are using JWT, which we will be using. So, that's absolutely correct again. And we would actually need others. But as for now, we will be concentrating on register. So, let me color this in orange. And we will create the login. Then we will create the session. Of course, the logout. And I will pause for a moment, because I'm not sure we will be digging into email verification reset password, even though I will explain you how to do that. If we have time, we can write it. Otherwise, we will need to move on. It depends on how things go. So, let me open a couple of notes. Okay. I've taken some notes, just not to waste your time. Okay. So, how do we want to implement authentication? So, this is the schema of what we want to do. Let's take a look at Heroku for a moment, where I have the database. Okay. So, this is what we have today inside the database. Again, we won't be digging into database and schema. You will see many nonsense at the moment, but it's just because we want to concentrate on authentication, nothing more. So, whatever you see here, it's not really important at the moment. Please be aware of that. We have a number of orders. And if you go here in profile orders, you will see them here. So, as you can see, a single order, it's basically a relation to a user, given date and our array of products. We have our products, as you can see here, with a number of information that we are not using, but they are quite handy. So, they are nice to have. Then we have users. So, let me delete the users so that we can start from scratch. So, what we will do is the following. We have a user that we will call, let's call it Alice. You know, when you're dealing with communication protocols, you typically use Alice and Bob as names, so we will be using them too. So, we have Alice that wants to log in to our server. So, she doesn't have an account. So, as a first thing, she has to create an account. So, she has to go to the register route. Okay. Sorry. Give me one second. I need to go on. Okay. So, Alice needs to register. In order to register, she needs to pass a couple of data. At the moment, we want things to be really easy. So, she will pass email and password. Okay. So, this is what she will be sending to the server. The server will need to look if the username and password already exist inside the database. If they exist, we cannot accept the request because, you know, the email is unique. So, email exists. At this point, if the email exists, we can go back to the user and say, I'm sorry, you can't do that. Oh, it's really, really ugly. Okay. So, let's do that. It may exist. In that case, 401, unauthorized, which is not correct. We should use maybe 422 or, yeah, let's just say that you are not authorized to complete that action, which is not 100% correct in HTTP verbs, but let's keep things simple. If the user doesn't exist, then we can proceed doing a couple of more things. So, for example, first of all, we will need to get the password and encrypt it. So, we will encrypt the password. Yeah, let me use password. Store it inside the database alongside the user. Then we will be sending a message to the user. So, that looks quite simple, right? But this is the most simple flow you can aim for. And it's not even complete because we will be finishing here, but in a production environment, you will need to validate the email, as we were saying. So, at this point, we will be ending. So, this is red because we won't proceed any further. We don't have time. But in a production environment, we will be sending a confirmed email. And how do we do that? This is interesting. If we want to confirm an email, we want to create a Univoc token inside the database. And maybe we can try doing that. We will store a unique identifier inside the database. We send it to the target email as a query string to our URL. And when Alice clicks on that URL, the server will say, okay, I have a request for that email address with that token. Is there a user with that email address and that specific token inside my database? If so, then we should confirm this email. Otherwise, we get an error. Quite easy. All right, everybody. Please let me know if you can follow, if I have to repeat again. So, that's quite a typical flow. After that, we consider our email to be confirmed and the user can start doing whatever they want. Yeah, let's do that. It wasn't planned for that session, but we're going to do that because it's really exciting. So, maybe if we have time, we're going to do that. So, okay, awesome. Let's start from there. Let's create a register api. So, let me take my users to the code. Oh, here it is. Okay. So, first of all, we will need, as you can see, we are using graphql request. If you want to follow along with me, I'm putting the api endpoint in the Zoom chat so that you can follow along with me writing code. So, as you can see, we are using graphql request for doing server-side and client-side requests using graphql so that we are connected to Hasura. If you're not familiar with graphql, please don't worry. I'll be explaining whatever you need to get started with it. Okay. So, that's just for saying. We will need a couple of libraries for proceeding with that. First of all, we will need to install bcrypt. Is there anyone knowing what bcrypt is? I can't believe no one knows. I know there's someone knowing it. Please raise your hand if you know it. Okay. Let me do the opposite. Please put your thumbs up reaction if you don't know what bcrypt is. Okay. Thank you, Oscar. Can all the other folks hear me? I'm sorry to fear that. Okay. Thank you, Billy. So, yeah, I guess you can hear me. Don't be shy, please, guys. This is a secure environment. No worry. Okay. So, let me show you bcrypt. So, this is the library we are going to use. bcrypt is a hashing algorithm and function used to encrypt and compare passwords. So, let me show you something. How many of you are familiar with MD5? So, for those who don't know, MD5, it's a cipher. It's an encryption function that takes an input, a string input, and producing an hash as an output. What does it mean? Let's see. It's an encryption function that takes an input, a string input, and producing an hash as an output. A string input and producing an hash as an output. What does it mean? Let's see it in the screen. So, let's say hello will produce the following hash. If I say Michele, which is my name, it will produce the following hash. But we can't store this inside our database. Why am I telling this? Let's see the first case. Hello. This is the MD5, the resulting MD5. Let's open another tab and put hello. Oh, as you can see, I'm switching between two different tabs. And for the given input, we will always get the same output. Can you see the problem? Can you see the problem? If there is a vulnerability in our system and we store passwords using MD5, then there is a highly security concern here, meaning that there might be someone exploiting the database, downloading all the users. And if there are two people, for example, using the same password, which can occur, they will have the same signature. Or we can brute force, meaning, okay, I want to, let's do that. I have this, you know, let me take this. Okay. I have the following hash. I don't know what the password is, so I can try with H. Is the same hash? No. Let's try with HE. Is it the same? No. So let's try with H-E-L-L. Is it the same? No. Let's try with H-E-L-L-O. Is it the same? Yes. So what's the problem with that? I can brute force this password, get the hello, which is your current password. And I'm 100% sure that 99% of every internet user out there, it's using the same password on Facebook, Twitter, Amazon, bank accounts, and wherever. So that's a bad security issue. Never use MD5. Please don't do that. That's really, really important. Amandeep says, we have various brute force tools available, which can give results in seconds. That's absolutely correct. Thank you, Amandeep. That's absolutely correct. We have a lot of problems with cryptography in general because we're also leading to the quantum computing era where, let's see if we can find it, brute force password calculator. I wanted to show you something. Thank you, Amandeep. That's really interesting what you're saying. Okay. So let's say I have a password which is stored as an MD5 and has four uppercase letters, four lowercase letters, one number, and nothing more. This is the number of combinations. So it's two billion, more than two billion combinations. We can crack this in 40 hours. So you see what the problem is. If I have an entire database of people and I can get this password in just 40 hours, that's a problem. I mean, that's a big security concern. So that's why I don't want to use MD5 and I don't want to use two-way password encryption, meaning if I give you twice the same input, I want two different outputs. Are you all right with that? Do you have any questions? All right. I will proceed. Thank you. Okay. So we will be using Big Crypt. But we can concatenate some secret string to the password. You're correct once again, and that's called salting. So we use salting proceeding that way. So I want to explain this to people maybe not being familiar with that. So I can use like, let's say this is my password. Hello. So this is user's password. And I can also use, let's say, node.js as a salt. So wherever the password is, I will use node.js dash hello. So as you can see, the output will be quite different. And what's the problem with that, Pavel? The problem is once I crack this password and I crack like three or four different passwords inside my database, I will see the salt in clear text. So I will notice that we prepend or append some other charters to the password. So eventually I will be able to understand which part of the password it's your salt and which part of the password is the actual password. So that's the problem. Salting in that case, it's not enough. I mean, it's an extra layer of security, but it's not really enough. And also there are rainbow tables that can solve this problem. I don't know if I can see a nice image for that. Okay. No, I don't want to dig into the rabbit hole, but there is an attack called rainbow table that it's able to crack password with salts by analyzing the MD5 hash. So it's not it's not a proper way to protect your password. Again, it's an extra layer of security, but it's not enough, sadly. So why are we using bcrypt? That's the interesting question for the moment. We're using bcrypt because if I give bcrypt the same password twice, I won't be able to get the same hash as a result. So that's really important. If I give bcrypt the same password 10 times, I don't want it to give me the same results. So let me show you how it works. I've just installed the package. As you can see, your bcrypt. Let me go here and create a little file like test.js. Let's import it. I hope the screen is big enough. Please let me know if I have to magnify it. Okay. So this is my password and it's hello. Really simple. So for the i equals zero, i is less than 10, i plus plus. So let's generate 10 different hashes using bcrypt. So bcrypt dot gens. Oh yeah, sorry. Okay. As Pavel was saying, and it's completely correct, we can use salt to enhance our password and using salts inside bcrypt makes the password even more secure. So we should always use salts anyway. So salt is equal to bcrypt dot gens salts and we can use like a number. The higher the number is, the more complex the final password will be. And of course, the more it will be slower to generate the password. So this is returning a promise. So let's wrap this into an async function. I wish top level await will end very soon. So await, we're generating the salt. Then let's just do console log await bcrypt dot hash. And we use my password and the salt. So let's see how it works. Okay. Awesome. So as you can see, we give the same password 10 times and we get 10 different password in return. And now you may be thinking, okay, that's great. I mean, no one will be able to get the original password from there, but how do we do, right? That's another interesting question. We don't. We don't need to. We just want to do the following. We have our password. So let's do that. Let's remove the loop. Let's remove this and say const hashed. It's equal to bcrypt hash. Then by default uses SHA256. Yes, Santiago. That's correct. That's the algorithm we are using by default. Let me copy paste it here because I can't pronounce this correctly. So this is the algorithm we are using. Great, Santiago. Thank you. So let's see for a moment what we are getting now. This is an example. Okay. And we don't want to reverse engineer the password. We just want to say dear bcrypt, thank you a lot for helping me. But now I have this hashed password. I have the plain text password here and I have the hashed password here. Can you compare them and tell me if it's possible that the plain text one is the same of the hashed one? So that's what we are doing with bcrypt right now. So let's do that. Const is correct. We can do bcrypt await compare. And as you can see, it asks us to send a couple of parameters. Plain text password and the hash. So my password and hashed. Can you see what we are doing? True. So we can do the following. Is my, let's say is hello, is goodbye. Let me show you what we are doing. Okay. So basically now I am giving the original password to the compare function and a new password which is wrong. Because as you can see, we are comparing with the hashed one, which is hashing the original password. So let's see how it works. Is hello true? Yeah, it's working. Is goodbye false? It's not working. Also, we did it. We did it. So that's what we are using. After the user sends us email and password, we will be storing that data inside of the database. So the password will always be stored as hashed. And again, I want this to be very clear. Please, if you have to implement authentication from scratch, please try to find any other possible solution. It's incredibly risky. Unless you're a professional working in the authentication strategies for like every single day in your current company, unless you are in that situation, I would discourage you to adopt custom authentication strategies because it's really risky. I want this to be very clear. I'm just showing you a possible way for doing that, but we will also analyze the good, the bad, and the ugly of that approach. Please be aware of that. Okay. So let's do this. Let's delete test.js. Let's go in pages, APIs, and as you can see, it's an empty folder. Let's create an auth folder, which will contain the authentication strategies. Register. I'm sorry. Are you all familiar with the next JS api routes? Okay, Santiago. Awesome, Amandib. If you're not familiar with that, please use another reaction right now. No, Oscar, no. Okay, great. I will explain this to you. Billy. Okay, okay. It's not a problem. next.js allows you to use pages for rendering html content. For example, we have the index page, which as you can see, takes all the products from the database and produces an html as an output. So this is a typical next.js page. But we can use api routes to create REST APIs inside of our next.js project. And it's quite simple, actually. We can do the following. Export default function handler. Let's make this async. Let's make this async. Just like in Express.js, fastify, Coa, or whatever, we get access to request and response. And at that point, we can say res json and say hello and word. Let's see how it works. Oops, not this one. Okay. yarn dev. Let's open the project. So as you can see, we are inside api out register. So we can now go here and say api out register. Hello, words. Hello, word. Just like we did. If we go back here and say hello, word, and we say I am Michele, for example, and we refresh, I am Michele. Here it is. Easy peasy, isn't it? I am Michele. Here it is. Easy peasy, isn't it? We can use this to communicate with the database. So one important thing, why do we want to use api pages? Because api pages don't get rendered on the client side. So browser, it's not involved in the computation of whatever you see here. So this is 100% server side. Never, and I won't tell this enough, never, never, never communicate with the database from the client, from the browser. Never. If I can't teach you something today, it's to never communicate with the database from the browser. Always do that from the client, please. That's really important. Okay. So we need to build our STPI. Okay. We will be using Insomnia. So as you can see, I made some tests today. We want to send email and password to our server. And we will get registered. So let's do that. First of all, we've seen we are sending JSON data. So email and password. Okay. So const, email, and password. We can extract them from request.body. Why are we using body? We could use query. But that means that we could also possibly accept get requests, which is really, really bad. We want every sensitive communication to be communicated using post requests, which get encrypted by SSL. So please never send get requests, including sensitive data. Always use post requests, which get encrypted. And okay. So we have email and password, but there must be some annoying problem. You know, people, maybe someone uses username and doesn't put any password or vice versa. So we can handle this. If we don't have email or we don't have password, just return, let's say, response, status, 401, unauthorized for the moment, res, JSON, success, it's false. Oops. And we get, give a message. Missing required parameter. We don't tell which parameter is missing because we don't want to expose how we manage things. So don't do that. Just say, we're missing something. Then we return. Okay. So let's try this. So let's try this. Let's just return. So if we send everything, we will get success through right now. So if we, if we go here and we pass email, a password success through, we are in that situation. But if we do that, oh, invalid JSON. Okay. I made something wrong here. Let's see what's going on. Why it's giving me. Oh, I'm sorry. I left a comma here. Here is what. Okay. Success, false, missing required parameter. And that's correct. We didn't put the password. That's correct. Awesome. Please stop me if you want to know more explanation that on that process. Okay. So now we need to import by crypt. So import crypt from the crypt. So now we may want to do some validation on the password. Let's do that really quickly. We can just create a function like function, validate, password, pass the password as an input. And we can say, if password length, it's less than say six, return false. Otherwise we do return true, which is a very silly validation. Don't use that in production, but it's just to let you understand the process here. It's not about validation. So if the password is not valid, we can just copy paste this. And with the message, let's do 500, whatever. The correct would be. Yeah. Okay. We should use this. Sorry. I had some confusion. So unprocessable entity. So let's use 422 message. Password is too short. We can try this right now. Let's try with hello, which is five charters. Oh no. Password is too short. So as you can see, we are also validating the password in a very silly way. Don't do that in production. Do something more, you know, strict, but this is something. Okay. Let's go ahead. We now want to hash the password, but before we do that, we have to understand that hashing the password, it's a very expensive function. And that's because it requires a lot of resources. The first thing we should do is to go on the database and say, dear database, please let me know if there's a user with that exact name. Oh, sorry. With that exact email, if there is a user, we will avoid hashing the password. So we have avoid some expensive computation before doing it. We want to make sure that we don't try to override a user. So we can go on Hasura and that will be really simple. Let me write the Santiago comment first. A pretty good use case of the api that one can create next.js is using this api as an intermediary layer between the client and the backend, allowing us to apply patterns such as backend for frontend. That's absolutely correct. Or even stronger pattern. And this helps a lot, reducing the complexity in the UI side, delegating the data processing and mapping to the server side. Thank you, Santiago. This is awesome. This is 100% correct. I do agree with everything you said. You should be here speaking because that's absolutely true and explained in a very good manner. Thank you a lot. So user exists. We pass an email as an input. And we can try doing that. So please give me all the count. Let me show it. Okay. I'm going to tell you what does it mean. So we're basically asking the Hasura server to count all the users with a given email. Please note that the email is Univoc. So we can only have one user with that email. So we can try, we can test this api by saying email. It's equal to michele.riva at Count zero, of course, because there's no one with that email. So if that is the case, we can register the user. Otherwise, we won't. Okay. So let's copy this. Let's import. Oops. Let's import graphql from graphql request. Let's write the query. It's equal to graphql. And we put the query we just created and tested. So now we can create a function like function. User exists. We pass an email and we import the graphql client. So as you can see, I'm exporting the client right now. The graphql client. So we are basically assessing the graphql client from here. This will be async. So an async function. And we say await. Or let's say const. Let's see. What does it return? It returns a user aggregate. So we can do that. It's equal to await. graphql. Request. User exists. So we are using that query. And we are passing a variable. So as you can see here, we are using a variable with the dollar sign. And one way to use a variable inside a query is to pass it as a second argument inside an object. So we pass email. And we can return boolean of user aggregate dot. I can't remember. Oh, yeah. Here is dot aggregate dot count. What does it mean? It means that if we are returning boolean of zero, it's false. Oh, yeah. Boolean. It's false. If we return boolean of one, it's true if we have like this many users, we have other problems to solve. So we should either have zero or one in general. So that's what we are going to do. Now we can say that if await user exists, so if inside our database we already have at least one user with that email, then we should abort and say res dot status to be 422 and send an error message. Like user already exists. So Mandip says, thanks, Michele. Sorry, it's late and need to get up. Oh, I'm sorry to see you leaving. Please look at the recording. Connect with me on Twitter. Let me write to you all my Twitter here. So you can reach out directly to me and I will be giving you whatever you need, basically. So if you need help or something, please reach out to me here. Awesome. Have a nice day, Mandip. So hope to hear from you. So Santiago says, in your experience, what do you consider what are the advantages of graphql compared with gRPC? Wow. That's a good question, actually. So gRPC has many pros, such as, you know, performances, mainly related to performances and communication between multiple clients, whatever. But if you want to implement a gRPC client on javascript, it's like hell. I won't be recommending you doing that. graphql, it's a protocol that allows you to do multiple things. For example, let me try to explain it. For example, we may have an api such as, I don't know, let's pretend we are Facebook, right? So please give me all the users. But then we may like to have like, please give me all the users, where friends is equal to, let's say, it's less than 100. And name is equal to Michele or whatever. As you can see, this can work with Facebook APIs, but it's not the same on Instagram. It's not the same on Twitter and wherever. graphql offers you the opportunity to kind of standardize all the communications from client to server, or even server to server, because you can communicate with another server using graphql. And you can also federate your api, meaning my data can possibly come from a database or from another api or from another different database, and I can aggregate the data inside one and only one endpoint. In fact, as you can see here, we are only hitting one endpoint, and we are passing a query. So we have a formal language for querying our data, and we don't have to think about what there is on the backend. It doesn't really matter, because we are using a language which is obstructing for us that complexity. So graphql is incredibly powerful, and we're just scratching the surface of its capabilities. But it's really important, in my opinion, to understand how to use graphql those days, because it's gaining in popularity. And I'm quite sure graphql will replace REST one day or another, just like REST replaces SOAP and so on. So I really hope you would be interested in learning more about graphql, because it's really, really interesting. I hope I answered your question, hopefully. Okay. We were saying if the user already exists, just return user already exists. If not, then we can spend... Okay. Sorry. I was reading your reply, Santiago. I'm glad to help. Okay. Now that we know that the user doesn't exist, we can put the user inside the database. So first of all, we need to encrypt the password. So const salt. First of all, we want to create the salt. So await the crypt dot gen salt. Let's say 10 rounds. Then let's create unhashed password. So await the crypt. Where is it hash? Here it is. We pass password and salt. Now we should have unhashed the password. Let's see if it works. So let's just return this. Oops. No. I've opened IntelliJ. I didn't want to. Okay. Close it. Okay. Let's try this. Password is too short. That's correct. I forgot it. Hello world. Password. Internal server error. Let's see. Oh. Okay. No. Sorry. That's my problem on Hasura. Let me fix this. I'm not going to explain why I just made a little mistake in configuring Hasura. No. Select. I didn't allow people to make aggregate query. That's on me. I'm sorry. Okay. Here it is. So as you can see, we sent hello world and we got that string in return. If we send it again, a different string. As you can see, at the beginning, we always have the same signature. So please concentrate on the end, on the final part of the hash. So let me send it again. As you can see, the hash is changing every time. So that means it's working fine. Super happy. Okay. Now we need to put everything inside the database. We can go back on Hasura api. Now we can say, please help me create a mutation. And we want to insert one user. And we will be inserting email and password. So we will call this insert user. We will need an email, which will be a string. Exclamation point means it's required. Then password should be a string again. Here we are putting email and password. We will be returning, let's say, the ID for the moment. Okay. Let's copy this. Let's create another query. Insert user. We'll pass email and password. Oops. I'm sorry. No, this is the query. What's going on here? I couldn't copy it. Here it is. Oops. So, okay. We are inserting a user and returning the ID. So let's create the function. Async function. Insert user. We pass an email, a password, and we say const. As you can see here, we're returning. This is the structure of the object we will be returning from the query. So we can say that we will be returning insert user one. We can destructure it. Say await. graphql.request. And insert user as a request. And pass email and password as variables. As you can see here, email. Oops. I misspelled password. Then we will be returning insert user one dot ID. So it will be returning a string. So, const. User ID. It's equal to await. Insert user. Email. And hashed. Okay. So we are passing the hash function here. So, if everything went fine, we can say success. It's true. And we can say I'm giving you the ID of the newly created user. So, are you ready? Should we try send? Wow. It's working. It's working. Let's see what's going on on the back end. User. Here is. And here is the password, which is encrypted. It's really difficult to brute force this. All right. We got it. Let's try again. Force. User already exists. And that's correct. Super happy for that. Let me recap everything. So, I'll get there. But I feel like this is beneficial for everyone. So, this is our api. We are getting the email and password from the request body. If there's no email and there's no password, it's useless for us to proceed. So, we just return missing required parameter. Then we want to validate the password. If the password is not valid, it's useless for us to proceed. So, we just return password. It's too short in that case. But any validation rules works. Then we will look if the user already exists inside of our database. If the user exists, we just return an error. The user already exists. So, we don't proceed hashing the password, trying to insert inside the database and having a conflict while inserting the user in the database. We don't want to do that. We just ask to the database if the user already exists. If the user does not exist, we generate a salt. We encrypt the password. And then we ask, we basically insert our user inside the database using this function. This function will make a graphql request for us, will insert the user, and will return the user ID. So, at the end of our chain, we will return success true and the ID of the newly created user. So, when we test this, we can try with another email, like like, we're not validating that email right now. So, let's try with a fake one. Foo at near Okay. We inserted it. And we have an ID in return. If we go on the database, we refresh, we now have two different users with the exact same password but different hashes, which is extremely important. It's not a simple topic. And I've spent many hours of my life, you know, trying to understand how to make authentication reliable. And the good part is whatever we are doing now, it's something we should never do. I mean, I'm showing you the principles of authentication, but I strongly believe you shouldn't, you know, realize handmade authentication. Because it's really risky, again, but we will see later working with Firebase how to make things a bit easier. We now need to understand JWT tokens. Best part of Hasura besides this has been reading Haskell. Oh, boy. Yeah. Haskell is my favorite language. So, I do understand your feelings. And, of course, it has a free tire. But also, whatever you're looking at here, it's absolutely free. Because I'm using the free Heroku database attached to a free Hasura instance on Heroku. So, it's completely 100% free. And that's the best part, of course, of the whole workshop, I guess. Okay. So, we need to understand JWT token. Okay. Please raise your hands or give me a thumbs up if you're familiar with JWT tokens. Only two people. Three people. Okay. Yes, okay, Douglas. Awesome. Okay. So, I'm gonna explain briefly JWT tokens for all the people that is not familiar with it. We're gonna do that using So, JSON Web Tokens are... Oh, thank you, Santiago. I see you're quite well-informed on the topic. So, please let me know if I'm saying something that is not correct. Because I feel like you'll be able to correct me. I'm not kidding. Please tell me. So, JWT tokens are basically signed JSONs, which are encoded into Base64 format. What does it mean? It means that the content of any token, which is this is the token on the left, it's visible to anyone. Are you scared of that? You shouldn't. Let me... Sorry. Let me go to the workshop repo. Because I've prepared a little demo to show you, and I don't want to waste time on it. So, we will be using FastJWT, which is a library created by NearForm, the company I work for. I highly suggest you look at this. It's probably the fastest implementation for JWT signing and verification. That's what we do at NearForm. So, again, if you're looking for a full remote job, please feel free to write to me. Okay. So, let me show you how JWT tokens works and why they are so secure, even if we can see the information they contain. So, I've already created something. So, I'll just copy paste them here. We will go in lib JWT, create an index file. Okay. What's going on here? We create a signer. We will go on that later on. Verifier and add the code. And as you can see, we need a secret token. Okay. At the moment, we will be using just the secret string. We can use, like, hello or a super strong pass or whatever. As for now, we are just using secret. Okay. We can also do that. That should be better. So, we put secret here. We can now go on the .env file and create a new .env property where JWT secret is secret, which is not really secret anymore, but it does the job. So, basically, next.js automatically injects all the environment variable inside any next.js api route. So, whenever you are on the server side, you'll be able to access secrets. These secrets won't be available on the frontend from the browser. If you want them to be available on the browser, you have to prepend next public. In that case, the JWT secret will be visible on the client side. Never do that in that case, of course. But it's just telling you that there's that possibility, of course. Okay. So, let's create a new route, and I'm showing you how JWT works. So, let's go in Auth, index.js. Oh, sorry, not index. Login.js. Let's import the sign at the moment from lib JWT. Okay. The sign method, as you can see here, will take, sorry, uses the create signer, which takes a secret key and will return a function. So, sign right now, it's a function that we can use to create a new token given a JSON input. So, what we are going to do now is create a simple handler. Oops. Let's do that. Request, response. And we are just returning res JSON. And let's say let's create a fake JWT for a moment. Sign. And here we use, like, admin is true. Name Michele Riva. Email is michele.riva at So, in that case, we are seeing a JWT, and we are sending it to the client. So, if we start the server, oh, first of all, we need to install the package. yarn add fast JWT. See? Awesome. Here it is. Okay. So, if we now go on auth login, we can just send a request. As you can see, we get a base64 string in return. So, if we take this and we go here to oops. Wrong folder. Okay. If we go here and go, like, in base64, decode. So, first website we find about base64, we paste it, decode. And as you can see, we are able to see the content. And as you can see here, we also have some strange charters. We will see what does it mean. So, let's go back on So, basically, we will use JWT token to say, okay, dear user, if you have that token that says that you're an admin, then you are an admin. If you have a JWT token that says that your email is michele.riva, sorry, michele.riva at, then I assume you're really michele.riva at But there's a problem. We can take the JWT, put it here, look at everything, and say, okay, let's change this from true to false. Okay. That's the problem we want to solve. Which is not a problem at all. And we can test this. If we go here, let's just do that. Let me see if I can rename this to mjs. Oh, no. I rename this to mjs. And I try to say, as you can see, we just modified the JWT. So, it was true, now it's false. I take this and I say console log verifier. So, I'm basically asking, given that secret, I'm asking to verify if that JWT has been modified by someone. Okay. So, let's just use it. Just because I need to use command line right now. Okay. So, if we go here and say node lib JWT. Oh, wow. As you can see here, we have the token signature is invalid. Why? Let's try to go here. And say admin is true. But my name is, I don't know, John Doe. And I use that signature. Which is random to me. I copy the JWT. So, copy. I go here and replace the old JWT with the one I just created. And we get an error. That's because we don't know what the secret is. So, we cannot sign the JWT correctly. But if we go here and say secret, which is the correct key for signing our JWT token, then take this. So, we copy it. We go here. Oops. And paste it. Yes. As you can see, now it's working. So, admin true. Name is John Doe. This is the email. And it should add. So, this is the timestamp at the moment I created the JWT token. So, as you can see, inside JWT tokens, we want to share information that shouldn't be private. So, don't store passwords. And don't store important tokens. Don't do anything crazy inside JWT tokens. Just say, like, I am an admin, true or false, my name, my email, nothing sensitive, please. And no one can modify the token without knowing the secret for signing it. So, did you understand why it is so powerful? Please thumb up, as always. Okay. Awesome. Santiago, Jacob, Basil, Amit. Awesome. I'm trying to see the difference between this and the MD5. The MD5 will give you the same result. And this one? Okay. Yeah. So, this is not encrypted. That's the difference. This is not an encryption. This is an encoding. So, if you go on base64 decode and say, like, sorry, yeah, this is Italian. Let's say hello. Oh, no, sorry. Let's go on encode. Sorry. If we want to encode hello, this is what we get. And given that, we can decode it and get hello back again. With MD5, you can't revert your hash. You can just brute force it. But you can't revert it to the original phrase. And why do we need base64, you may ask. That's because we typically pass this through HTTP requests. And we don't want special charters to appear. And given that, we are using JSON. So, the content, as you can see, there are different colors. So, all the colors are divided by a point. So, we have three different, sorry, two different JSONs and a signator. Meaning, the first part of our JWT will tell us the algorithm we used to sign the token. The second part will tell us the token content. And the last part is the signature. We can choose the algorithm used to sign the token here. And as you can see, sometimes it requires a public key and things get complicated. So, we don't want to do that. So, in that case, our signature will be, as you can see here, we basically encode for URS the header. Then we get a point. We encode the payload. So, .JSON. And then we encrypt the, sorry, encode the secret. So, that's what we are going to do. We don't want to hide information. So, that's the difference between MD5 and JWT. We don't want to hide information. We want the information to be easy to read, but really hard to modify. Unless you know the secret. So, that's the power of JWT tokens. And this is what we will save inside a cookie to tell that the current user is logged in correctly. Is it clear enough? Yes. Thank you very much. Okay. No problem. No problem. Happy to help. So, okay. That's what we are going to do. So, first of all, we will need to get the user and the password from the, sorry, the email and password from the request. And that's the exact same thing we are doing here. Sorry. Here. So, if there's no email, there's no password, missing required parameter. Easy peasy, lemon squeezy. Then, if we have the email and we have the password, we want to ask our database to tell me, to give me all the information we need to validate that request. So, in that case, we will go on Hasura and say, user, please give me all the users where the email is equal to email, which is a variable. So, get users by email. Email, which is a string. And we will return all the data that we need inside the JWT token. For example, the user ID, maybe the name, if we have it. The email. And we will need the password, even though we won't be including the password inside the JWT token. Again, never store sensitive data inside a JWT token. So, let's try this. As you can see, this is returning us the ID, name. We don't have a name for now. Email and password. Okay. Awesome. So, let's copy this. Let's go here. Let's import graphql from graphql request. Let's create the query. So, we have graphql. Okay. Get user by email. Awesome. Now, we want to create a function. And also, we want to import. Okay. So, we want to import. Okay. So, we want to import. Okay. Autocomplete. Please don't do that. To import graphql from lib, graphql. Now, we want to create an async function. Get user by email. So, we pass an email as a permitter. As you can see, we will return an array of users. As you can see, user, it's a list. It's an array. So, const user equals graphql.request. We use the query we just created. We pass the email. And we return user. Let's take the first. So, given that we only admit one user for one email, we will be getting just the first user we get in return for this query. If we have no users, we will be returning an empty array. Okay. And this will be await. So, let's do that. Const user is equal to await get user by email. But if, okay, no, sorry. Don't return this. Let's just return now. So, if we don't have any user, because maybe we just, you know, we just put the wrong email. We made a typo. Whatever. Rest. Status. 404. So, not found. We couldn't find this user. Invalid email or password. We don't want to say user not found. And there's a reason why. So, let me rephrase this. We don't want to use, we will use unauthorized. Is there any idea why we don't want to say that we couldn't find the email? No ideas. security. Great, Santiago. Yeah, that's correct. So, imagine. I'm trying to brute force. And I know that there's, yeah, I'm giving clues. That's correct. So, I know that, let's say, Michele Riva works at Neoform. So, I'll try to log in for her. So, I'll try to log in for her. Yeah, I'm giving clues. That's correct. So, I know that, let's say, Michele Riva works at Neoform. So, I'll try to log in for him. And I try Michele, Michele Riva, all attached without spaces or dots. And the server says, I can't find the user. But then I try Michele dot Riva. And the server says, wrong password. What does it mean? It means that the user exists. The email is correct. But the password is wrong. So, as Santiago was saying, we are giving clues. We don't want to brute force, to let people know when to brute force. So, as an extra layer of security, we never say this user exists or it doesn't exist. We just say wrong user, wrong email or wrong password. So, that's all we are going to say. Okay. So, if we are here, it means we have a user. So, we can create a JWT token. We have the sign method. So, let's create it. Create a JWT sign. And we will be put, oh, I'm sorry. What I'm doing. We need to understand if the password is correct before doing that. I'm really sorry. So, let's import by crypt. Okay. So, is password correct? It's equal to await by crypt dot compare. And as we were doing before. So, we passed the plain text password. So, the one passed as a post request. So, password. And then user dot password. Which is the one we are getting from the database. That will only return true or false. As you can see, it's a promise containing boolean. So, true or false. Now, if it's not correct password, we just copy paste this. And again, of course, I'm repeating myself. It's not good code. But again, it's just for understanding the flow. So, invalid email or password. So, if the password is valid, though, we want to sign the JWT. So, JWT. It's equal to sign. And we put the ID. So, user dot ID. Email. So, user dot email. Name. User dot name. That's all we need. So, res dot json. Sorry. Okay. We will do that. We will reply success. It's equal to true in that case. But we will never, never send the JWT token to the client. Instead, we will save this as a cookie. That's super important. We will save that as an HTTP only cookie. Which means that javascript from the browser will never be able to reach that cookie for security reasons again. Because if I create a browser, it will never be able to reach that cookie. Because if I create a browser extension, a malicious script, whatever, that says, please, document.cookies. Take them and fetch and push this to my server so I steal the cookies from anyone on that website. That's really bad. Never, never, never save JWT tokens inside cookies. Never do that. Never. Please use HTTP only cookies that are designed specifically for that kind of usage. Is it clear? Do you want me to explain this a bit more? Okay. Thumbs up from Santiago. I assume that's fine. Thank you, Billy. Awesome. So we need to install one last package here. Which is called cookie. And as you may guess, it's used for dealing with the cookies in javascript. Let's import it. Import cookie from cookie. Now we want to say res set header because we need to set a cookie. So set cookie. I guess we should do that this way. I'm blinking out. So let me see for a moment. I made some tests before joining the workshop. So let me see. I'm quite sure we did it this way. I just want to make sure. Set cookie. Perfect. Cookie.serialize. We need to give a name. So we can call it authorization. We can call it foo. Whatever. This is not really important. Oops. Then the value for the cookie, which must be a string. So we pass the JWT token. And then some options which are incredibly important. For example, HTTP only. True. Domain. We can say, okay, this cookie will be only valid on, let's say, Or every single domain inside For example, data.Amazon. I don't know. Whatever. Staging.Amazon. We can do that. So third level domains can be included here. And also wildcard domains. We won't do that for the moment. But we want to say, expires. So after how much time do we want this to expire? And this is the property we typically set when the user checks, please remember me. So we can say, expires. If I remember correctly, it's in seconds. Let me see for a moment. Oh, sorry. No, it's max age. So instead of saying, please expire on that date, we will say, this cookie will be valid for 60 seconds times 60 minutes times seven days. So this is how long the cookie will remain. And believe it or not, we did it. So let's try this. So my email was, you said seven hours. Douglas, you might be right. It's becoming late, and I'm starting to blanking out. So you might be right. You might be correct. Okay. So my email, I registered with the email, My password was hello world. Are you ready? Let's try this. Oh, couldn't connect to the server because I didn't start the server, of course. So let's try this. Okay. So let's try this. Okay. So let's try this. Okay. So let's try this. Oh, wow. We did it. Let's try. Okay. Here is the cookie. So if we now take this and go here, as you can see, this is correct. This is my ID. This is my email. I don't have a name in the database. So we did it. Awesome. So we just created a login. And yeah, I see the party reactions. Great. So we just created a registration and login method for our next.js app from scratch. Before we move on, I would like to ask you, what are, in your thoughts, the good, the bad, and the ugly of that solution? So anyone has any idea, please feel free to use your microphone. No, and don't worry. I see Santiago saying that needs to drop off. Thank you a lot, Santiago. It has been a real pleasure. I see you're really interested in this. I mean, and you have some expertise, of course, because great conversations. Make sure to connect on Twitter. I'd be glad to explain if you need some explanation. So yeah, have a nice rest of the day. And okay, Oscar, you were saying something. No, I wasn't saying, but I can say the bad is like, it's so many details. And if you make one little, it's a lot of, you have to know quite a bit to do this. And you have to be sure. Exactly. I agree. I do agree. The good is like, you will understand it, and then you master the other things. So anyway, so that's what I like. What I see is, like you mentioned, never put anything in local storage or IndexedDB and that. So use the HTTP-only cookie, right? That's what you will use if you have right? That's what you will use if you have an application that you want to have a session, for example. Information like to do that. Okay. You do it in the HTTP-only cookie, I guess. Yeah. And so let's do that. I'm going to give you my ideas on the good, bad, and ugly. Then I'd like to propose you how to proceed, because we have one more hour. And okay, no, let's do that. I have a proposal for you. We have one more hour. We can decide two options. One, we move on understanding how to integrate Firebase. Two, we attach this authentication mechanism to the frontend together. I show you how to do that and how to deal with the data we just created, because how do you access the JWT token if it's not available on the client side? So there are resources online explaining that, but I'd like to show you. So personally, I'd like to proceed working on this one, and I will give you some generic information about Firebase of zero that you can find online. So I'd like to ask you, what do you want me to proceed on? Please turn your thumbs up if you want me to go on Firebase. I see a no. I see Victor asking for option two. Okay, so let me do the opposite. Please thumbs up if you want me to work on option two. So integrate everything together, make it work, and then give you some information on Firebase generically. Awesome. Yeah, yeah, okay. Let's do that. Great. We have one hour. Let's do that in one hour. We went really far in just two hours, so that's really, really great. So in my opinion, the good is we have complete control over the whole authentication flow. Having the whole control of the whole chain is a blessing and a curse, meaning we perfectly know whatever happens. So if there's something not working, we may know where to put our hands to fix it. We don't have any dependency on external services. So this is great. We do understand how things work, which is really, really fine. The bad is our code won't be as beautiful as, you know, the one written by professionals working in the authentication industry. That's something to consider. We have to behave in a way that whatever we do is secure. We have to understand the risks of implementing authentication from scratch. So I find this to be interesting as long as we do that for learning purposes, known for production usage. The ugly is that there are companies investing billions in making authentication secure, and I wish you'll understand that we don't want to use our own authentication mechanism. I'm showing you how authentication works at a high level, but there are companies doing that professionally for a very long time, and they do something that we wouldn't be able to replicate alone. So please rely on professional external services when you have to deal with authentication. That's really, really important. Okay, so let's move on. Let's try to attach the login part to the front end. Okay, so we have our api. Let's go in login. So as you can see, we have a simple form. Let's just use our useState from react. So if, oh no, what I'm doing. Say email, set email. The same for the password. Now, here we say, okay, the value is email. Here is password. On change, we got an event. Let's call it E, and we want to set email, event, sorry, E, target, value. In that case, set password, of course. And we can try to debug this, like console log, email, password. So if we go here, we go in login. As you can see, it starts with empty. So ciao, which is hello in Italian, and hello. So it's working pretty well. Once we are done, we want to submit the form. So we can say, button here, can go on a new line and say, on click, handle login. So we can create a function, handle login, which will fetch the api we just created. So fetch api. Where is it? I can't remember. Out. Perfect. Login. And then we need to pass a couple of options, like method, which is post, as we've seen. Body, which is adjacent, we need to stringify. So we're going to pass email and password. Then we can do that. We can import user router from next router. So we can use the router hooks and say, okay, if everything went well, we can say router.push to profile. So we can see the profile page finally. If there's an error, for example, user name or passwords are wrong, that's the case for an error. For the moment, let's just log it. We are not really concentrating. We could show a banner, we could show an error message, whatever. Let's try this. So kele.riva at Let's start with the wrong one. With the wrong one. It's doing anything. Login.js. Oh, no, what's going on? Let me refresh for a moment. Maybe it was cached. Oh, okay. It's not working properly. Let me show you why. Sorry. If you go here, you see preview. Missing required parameters. Email and password. Okay. Let's see why. So if success is false, because we are entering here, so we can say data, return data.json. Then we can do that. If not, sorry, if success router.push to profile. Okay. So we always return success, which can be either true or false. But in that case, I'm putting random data. Okay. Awesome. We are just getting an error message from the server. We could show something here, not important at the moment. Missing required parameter. Why? Okay. That's something I'd like to understand. Let me see for a moment. Blinking out. So fetch. Post. I know I'm doing something wrong here. If you have any idea, please tell me. We should say, okay, json stringify. No, it should be correct, actually. Let's see on the server what's going on. I just want to see request body. Let's try to debug. So, of course. No, that should be correct. So, we have a problem here. It says missing required parameter. So, either email or password right now, one of them is false. Let's see why. Oh, they are undefined. Oh, maybe I have to... Sorry. I'm really sorry. That's right. Quite sure that's why. Awesome. Yeah. Okay. Sorry. My bad. My bad. We have to stringify. I couldn't remember this. Okay. Great. Okay. So, now it should be working fine. So, okay. Of course, we are getting an error. As you can see, unauthorized. Which is correct because I'm putting random data here. So, wrong email and password. So, now I'm putting the correct ones. The password is hello world. Yeah, we did it. So, we are logged in. That's awesome. How can we ensure that we are really logged in? Let's go and see the cookies. Let's try to refresh. Okay. It seems like we are not saving... Oh, no. Let me maybe... The browser extension maybe has no access. Let's try to see here. In application, cookies. Okay. It looks like we are not saving. This is another session. So, we should see the out cookie to be set. I'm probably using the wrong keyword here. So, let me see for a moment. Because I was sure these were working pretty well. So, cookie.jnpm. Wrong package. Okay. Let me see for a moment. No, that should be correct. Set header. Let me copy paste this. Yeah, it's correct. Set cookie. Max age. Let me copy this. So, this is one week. Of course, as Douglas was saying, I forgot to multiply for times 24. Okay. No, that should be working just fine. Let's see what's going on here. Let's try again. So, let me hard refresh. Go here. Hello, world. Okay. I don't know why it's not showing up here. Okay. You know what? We are going to fix this in a moment. This is something I wanted to show you on Firebase. But we are going to implement this right now. Because I don't want to waste any time. But it's working indexed out the same way. I don't know if any one of you is familiar with iron session. Is anyone familiar with that? Thumbs up in the case. Okay. I guess this is a no. So, iron session, it's basically a session manager which uses cookies underneath and encrypts them. So, extra layer of security. We can also store sensitive data inside cookies because they get encrypted. And there's an awesome package called next iron session. Oh, okay. Yeah. So, let's go here. Let's install this. So, they basically manage cookies for us. Okay. We got it. Now, this is an example with next JS. So, it's pretty, it's on our case. So, let's go here and say with iron session. Okay. And we basically want to do that. So, we go in lib, we create an out folder, an index file. And we create, so we want to export const cookie settings. Which will be an object. So, cookie name that we will be calling out. A password that will be process. And env dot, we can use whatever we want basically. Like the JWT secret or whatever. Let's use this for a moment. Secure, we can use secure in production only so it's easier for us to debug. So, now we can import it. Like import cookie settings from, I changed my keyboard to the English layout from the Italian one. It's really difficult for me to type correctly right now. Awesome. So, as you can see, it asks us to wrap the entire handler. So, we can do that. Const. Oh, sorry. As in function handler. And we can do that. Export default with iron session api route as you can see here. Handler. Handler. And we also want to pass the configuration as a second parameter. So, we say, please, these api routes wrap it inside the iron session handler. And also pass the cookie settings so that it creates and reads encrypted cookies. So, what we do now is to go in, okay, we will get a new property inside the request. So, we can access the, we can say res dot session. We can finally access the session argument. Sorry, session parameter. Res session. As you can see, user, for example. And we can say session dot JWT. It's equal to JWT itself. Okay. And then we have to use this method to save the session. Let's try and see if it works. Go back here. Log in. Let's open here for oh, yeah. Your dev. Let's refresh. Okay. Oh, yeah, I know why. Bad usage. Password must be at least 32 charters long. Yeah, that's cool. As you can see here, we're basically passing the JWT secret. Let's write a very random one. Because it wasn't secure enough and iron session prevents us from using a secure password, which is really, really fine. Really good. So, let's try again. Okay. We got another error. Let's see. Okay. What's going on here? I'm really sorry about that. But let's see. Oh, I got it. As you can see, I'm putting this to res. We need to put it to the request, not to the response. So, eventually, we can try again. I'm sorry. I wasn't expecting to do this. Okay. So, that's what we got. As you can see here, we have the out cookie, which is HTTP only. So, if we go here and say document.cookie, as you can see, it's empty. So, javascript cannot access this cookie. This is really important. And this is encrypted. So, if we go here on basis64, the code, as you can see, it's completely useless. So, it's an extra step of security, not really needed if you're using JWT tokens. Still fine. Still fine if you want an extra layer of security. So, is it clear why are we using iron session? Do you want me to repeat quickly? You're using iron session to save the cookie in the session, but why didn't you... You tried to do it by scratch, and it wasn't doing it. So, you're using iron session, right? Yeah. Okay. I see Hamet asking for clarifications. So, I'll give you some, of course. The idea is that I was trying to manually set a cookie. And maybe I was... I don't know. I made some mistakes and I didn't want to waste time. So, whatever we were doing by hand, like response.setheader. So, we were trying to say, okay, dear browser, I'm sending you a response, which includes a new cookie. So, the browser should take this cookie and save it HTTP only. So, only the servers will be able to access this cookie. There was some kind of error that I wasn't expecting. So, instead of try to debug and waste some time, I wanted to use the iron session, which does the exact same thing. And I'm telling this specifically for Hamet, which asked for clarification, does the exact same thing. So, it basically takes a parameter here. So, it creates a new property inside the request object. It creates the property session and say, okay, inside that object, we put the JWT property. So, we will end up having request that it's equal to session, which is an object that has JWT, that has the JWT string. Okay? Then we say, okay, now save it. And by save it, we mean save it as a cookie, because iron session basically saves the session as a cookie, just like we did, but correctly, because it's clear that I made some mistakes here. And also, it encrypts the data, which is an extra layer of security. We don't always need it. But it does it for us for free, so I guess it's fine. Is it more clear? Do you need more explanation? Save it where? In the server? No, it saves it on the browser. On the browser. But it tells the browser to avoid access to this cookie to javascript. So, on javascript, if we say document.cookie, as you can see, we are not able to see that cookie, because we are basically telling the browser to hide this from javascript. And it is real. So, anytime you sign in as that person, then you will see that cookie. Is that right? Excuse me? I couldn't understand. That cookie is also, when you come back later on, you should be able to... Oh, yeah. Yeah. As you can see, the expiration is for the 9th of March. Okay. So, yeah. Until that date, we will be able to access this cookie. Okay. Okay. And great. So, now there's a problem. We are logged in, but if I delete the cookie, I'm still able to access this page. So, it's not really useful. We now need to protect these routes. Right? So, let's do that. We will need to create a hook. And it will be pretty easy, actually. We can go in lib, use... No, let's go on auth. Hooks.js. And we can create, like, import, use effect from react. I don't know why it's auto completing like that. Export. Let's call it function. Use auth. So, let's import. Use effect. Sorry. Use state. We will need a couple of states here. For example, we will need loading. Set loading. Use state true. So, by default, whenever we use this hook, we start to load a loader. And we will see why in a minute. User. Set user. By default, it's null. And I feel like, yeah, we can use error if we want. Any error. Don't do that for the moment. We will return. Loading. Set loading. Sorry. And user. We will also get a property called logged in. Which, if it's not loading, and we have a user, so that will be an object. So, object. Keys. User. Length. And that's the only way we have to control if the object is not empty. So, if the object is not empty and it's not loading, that means that we have the user. Okay? So, as soon as we load on the client side, we say use effect. Okay. So, use effect. Fetch. And we will need to create a new api. api, out, get, session. Then, we will get a response. REST.JSON. Then, set user. REST.user. Let's do that. Then, oh, sorry. Catch. If we have any error for now, let's just console log. Finally. So, we don't really care at this point if it succeeded or failed, but we will stop the loading function. So, set loading. False. So, it's not loading anymore. So, we will need to create a new api for getting the session and giving it to the client. And we will do that by going into pages, out, and creating the api route. So, get, session. It will be quite similar to the register one. So, I'm just copy-pasting it. But, oh, sorry. Not, I mean, the login one. But will be way easier. So, we can't delete all of this. We just need the next iron session. Okay. So, now, if we go here, let's try to log in from, oh, what's going on? Why it's not logging on here? I don't know. Never mind. We left a comma. Where did we left a comma? Oh, you're right. Thank you. I don't think that's the reason why. It isn't giving me any useful explanation. Oh, yeah. I know why. Yeah. Here's why. We are parsing. We should do that. Type of rec body string. JSON parse rec body. Otherwise, let's just return rec body. That's not the reason why. Oh, yeah. No, it's because I'm stupid. That's why. Okay. Now, it's working. Yeah. And as you can see, we have the cookie. Okay. Great. So, I just want to test it. I did that just to save a cookie inside Insomnia, which is the client I'm using right now. So, now, if I go on here, we got return. Let's try with res JSON session. And we can try to put request.session.jwt. Let's see what we get. So, get session. Okay. We have an error. Oh, yeah. I didn't import the cookie settings. That's correct. Let's try now. Okay. Let me see how to... Because they changed the library right now. Oh, okay. No, that should be correct. Let's try this. Request session. I'm not sure Insomnia is able to... Oh, yeah. That's why. It didn't save the cookie. Okay. No. We will debug this in another way. Maybe we can just use this, but we... Oh, no. Of course, we can't. Okay. No. No problem. Sorry. Sorry. So, we get the cookie like this, const request, say, cookie. So, let's call it jwt. So, here we get the jwt, right? We also want to decode it. So, I can't remember the name, so we can just import it as follows. lib jwt. So, now we can see the exports. Oh, no. Why is it not showing up? Let's go here. Decode. Okay. So, we get the jwt stored inside the session. We now get... Now say const decoded is equal to decode jwt. We then send the session back this way. So, I can't remember. We call it res.user. Now, let's call it session. Okay. So, we are basically saying there is a session inside of our request, which is called jwt. Oh, hope to see you soon, Basile. It has been a pleasure. I hope you enjoyed the workshop. Yeah, let's connect on Twitter if you have any questions, of course. So, we are saying... Oh, thank you. Let's take this jwt, decode it, and send it back as a JSON. So, we can try to use this hook now. If we go, for example, in components, we can create a wrapper, an out wrapper. Export default out wrapper, which takes a children component and does the following. Imports the use out hook from leap out, and here we are. Okay. Now, it also uses... No, it doesn't need to, and we can say return. Let's use this const. Loading is logged in. Use out. So, what we do is, if it's loading, we can return a loader. Okay. If the user is logged in, and here we should pass a couple of props. Let's say we just passed children. So, we only show this page if under authentication. So, if it's logged in, let's return props.children. Okay. If it's not logged in, we can use the following. Import use router from next router, and here we say const router is equal to use router. Now, we say, if it's not logged in, just router.push to 404. So, route not found. And return now. So, we can also do the following. We can return, but maybe we will do that later, props.children, and we can also pass user as a prop. So, we can, as you can see, we are returning user here. As you can see, we are returning user here. So, we can return user. So, that every single part of our application that is wrapped inside the logged in wrapper will have a user prop by default. That could be working. I'm not sure, but we will try. We have half of an hour. We will try to do that. Let me drink some water, and we will do that. Okay. So, let's go now in app.js. Okay. Here, we can import again the use router hook. We also import the wrapper. And we say, let's try it to router.spath. This is the property we want to test. So, if we go here, let's refresh. As you can see, profile. Profile, change password. Products and the product ID, right? So, basically, we want to say if we want to create an alias, let's call it layout wrapper. So, we just return whatever we have here. But here, we pass props children. So, now, we can basically say, if router.spath starts with profile. So, if the current route starts with profile, we want to return out wrapper, layout wrapper, and component. Otherwise, we will be returning just the layout wrapper and the component itself. So, we basically moved a couple of things around. Let's try and see if it works. Okay. Don't worry about that. But as you can see, it's working pretty fine. Let me delete my cookies. Awesome. I already deleted them. So, if we now go on login, it should be fine. But if I go on profile, oh, use out is not a function. Okay. Let's see what's going on here. Auth wrapper. So, it complains with use auth. Oops. What did I do? Oh, yeah. Hooks. Sorry. I imported the wrong file. Okay. Awesome. It's working fine, as you can see. So, what we do is, you can navigate any route, but if the route name starts with profile, as you can see, it will push you into a 404 page if you're not logged in. So, now, you can go on login and say michele.riva at Hello, world. And I'm not sure it's working. Let's hope for it. But let's try. No, it's not working. Let's see why. Okay. Okay. Let's try and understand why. Now, we can try to debug. But we are almost there. Okay. So, we are here. Let's see on the network what's going on. This is the hook. I fear we didn't call this one. So, let's see what's going on here. Oh, thank you, Vitor. I hope you enjoyed the workshop. Let's connect on Twitter or LinkedIn so we can continue the conversation. I hope you enjoyed it. Thank you for being here. Let's try to debug. So, okay. I fear there is a problem. Give me one very second. I need to check one thing, which is private. But I just want to make sure it's working properly. As you can see, the hook is continuing to call itself. I just want to make sure I did the right thing here. Sorry. Give me one second. Okay. Yeah, it should be working fine. Okay. Now, let's do that. First of all, if we are not logged in, let's go to the login. Okay. So, that we can see what's going on here. So, this is the get session. We have some problems with the get session. As you can see, this is the cookie we are passing. And the get session api is going into an error. The token must be a string or a buffer. Okay. I guess we are having some problems where we let's go on pages, APIs, get session. Okay. I guess this is undefined. So, I'd really love to see rec.session. And I'm sorry I'm taking too much time on this, but I wasn't expecting to be working on this one. I wasn't prepared, but I see you all look quite interested in this. You can mute your microphone. I'm really sorry. And okay. So, as you can see, yeah, we can't access the session. Let's see why. Okay. I feel like there's no reason why this shouldn't be working. We can do that. Give me one second here. I fear that we can't save the JWT inside. So, let's just put this. So, let's try avoiding putting a JWT inside the session. Rec.session, awesome, get session at this point. Session JWT. Okay. At this point, that should be it. Let's try. Let me delete my cookies. Log in. Okay. So, we got our authentication. And also, that's a decent error, I guess. Let's see if we can see. Okay. Let's see if we are able to access the request. No, I guess we can't. And so, it's logged in. I want to see user. But actually, oh, so it returns an empty user. I fear we have some problems with the iron session. So, as you can see, it's undefined. I don't know why it's not saving the session properly. As you can see, we are basically doing whatever they ask for. I don't want to waste time on this. So, it's important to me that you get the point of what we did here with the out wrapper. So, this is where we manage the authentication. So, we basically say, if that route is protected with authentication, we call the server because we don't have access to the cookies. We call the server and say, okay, server, please decode the cookies. Send the content to me so that I can show the page content to the user without having to show him the cookie content itself. So, just send me the minimum data you need. So, while it's loading, of course, we will be loading. If the user is logged in, show the page. If it's not logged in, let's push the user to log in. Now, you may be thinking that provides us from server-side rendering, authorization protected pages, right? Do you think that's a problem? Open question. Thumbs up if you think that this can be a problem. I guess that's a no. Okay, Douglas, you say that might be a problem. Why can it be a problem? That's my second question. Let me stop sharing because I fear we are running out of time, but given that search engines won't be indexing private content and given that server-side rendering, it's really expensive on the server and it's really slow, especially with react, we don't want to serve private pages using server-side rendering because all the content we need, it can be rendered on the client side once we ensure that the user is logged in properly. So, there's no reason why we may want to server-side render private routes. From this workshop, I wish you can take home a couple of learnings. First one, avoid server-side rendering whenever you can. Even though next.js is built for server-side rendering in react, I'd like you to prefer static-side generation and incremental static regeneration when possible. I prefer client-side rendering for dynamic data and use server-side rendering only when extremely needed. If it's not needed, please avoid it. And you only need server-side rendering when you have content that must be indexed by Google, by Bing, and whatever. And I can't find many reasons why we should server-side rendering, but that's for another workshop, of course. So, it's another problem. That's what I want you to understand. We want to hide as much as possible until strictly needed. So, that's another learning, in my opinion, for managing authentication in next.js. Until we are not sure that the user is logged in on the frontend, we won't be showing anything.
155 min
22 Feb, 2022

Watch more workshops on topic

Check out more articles and videos

We constantly think of articles and videos that might spark Git people interest / skill us up or help building a stellar career