Next.js Authentication - Avoid these 4 mistakes (Don't do auth in layout!)

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
next s introduces some new paradigms server components server actions and they influence how we should do authentication so in this video I'll discuss some best practices and also some really important pitfalls to avoid when it comes to authentication in nextjs so let's say we have a very simple website here I just have a homepage let's say that we want to have an article page but only logged in users should be able to read this article right so right now all of this is public we have zero authentication so for the examples in this video I'm going to use kind but the examples I'll show you work similar for any other solution that you may be using so if you watch my channel you know I'm a brand ambassador for kind it's a paid sponsorship but I had a really great time working with them so far and they actually have a very generous free tier to get started so they are the sponsor of today's video okay and if we take a look at the folder structure it's very simple we just have that homepage right that's what we see here it's this page and nothing crazy going on here and then we have our SL article page which is the one that we want to protect right so this one has some text that only logged in users should be able to read right right now everybody can read this so we need to add authentication to our app so here in my kind dashboard I can just add my application here it's actually a backend application so I will pick that I will save here then here for the sdks we're going to go with the nextjs SDK all right so we already have an existing code base here I just need to pick these call back URLs and then I need to install the kind package so I'll just open up my terminal here and just paste that right here all right next is to add some environment variables so I'm going to create a env. loal file in the root and I'm going to paste all of these here and then the last step here is to create this API endpoint here I can copy the entire path like that and just paste that right here okay and then I need to copy this and paste that into into this it will create an API endpoint we have now properly set up kind now as you can see it's still public right everybody can just go here so now we actually need to do an authentication check somewhere so what a lot of people are inclined to do is to do it in the page component right so on the server side and remember these are all server components by default in nextjs so we can mark it as async and on the server side whether it's a server component server action you can use this one function from kind and it will let you know if the user is authenticated so then here we can make a check we do need to call it because it's a function so uh I will write it like this so if the user is not authenticated well what we actually want to do is just redirect them to the login page all right so now if I try going to the article page and see what happens and I'm being redirected here to a log page hosted by K so here I do need to let K know how I want users to authenticate themselves right so here under authentication uh we have some options here email and password but we're just going to go with GitHub here so now when I try doing it again if I go to read article I'm being redirected here and now we have this GitHub button I will just log in and let's see what happens it's going to redirect me back to our Local Host 3000 and by default actually it will redirect to dashboard so let's quickly change this in the environment variable here so we will just redirect as a default to SL article right so now if I go to the homepage um and try to go to the article page I can view the article page because I am logged in and I can see that as well if I go to my cookies here you can see I have my access token right here okay so I'm logged in so that's why I can view it now so now that we've seen an example of simple authentication here let's talk about some best practices and some Pitfall so number one is doing authentication in layout which is what some people are tempted to do so here we have our root layout which wraps essentially all the pages so what people are tempted to do sometimes is for example when all pages need to be protected right so if I also want to protect my homepage here very common in a dashboard type of app where basically everything is behind a login well you would have to do it for all the other Pages as well right so then I would have to go to homepage here which is this page file right so then here we have our homepage I would kind of have have to duplicate myself and I would have to make the same authentication check in all of my pages right so you may be inclined to say well what if just do it in the layouts right so I can remove it from the individual uh pages right so then I can remove it from these pages I can remove it from here and then I can just do it here in the root layout component which is also a server component I can mark it as async and here I can technically do the same check right so now the idea here being that I have protected my whole app all the pages all the routes now unfortunately this is not a good idea so let me actually show you what happens when you actually uh navigate right so this will indeed this code here Will will run the first time you actually come to the page and so if I actually load the page well the root layout has to be loaded the it has to be rendered the first time so this code this authentication check will indeed run but now that the app is loaded now if I navigate to another page let me actually show you what happens here in the network tab so here if I now click on read article I'm navigating to another page and a page is just a server components right so here article this is just a react components it's a server component and so when you navigate to a different out you're essentially just fetching a react server component it's not like a hard navigation where you get a completely new HTML file it's just one server component essentially that you're getting and therefore this root layout is not going to reender when you navigate right so just to show you that I have commented this out I'm going to show a console log every time this layout is rendering right so if I save here you can see layout rendering right so if I just refresh you you can see layout rendering but if I navigate to another page you can see it does not show show that it's rendering again so what's happening here as we navigate it's just going to fetch a react server component so this can be fetched independently from the layout so if you do the check in here you're not properly protecting this fetch here that somebody could make so conclusion is essentially that you probably don't want to do the authentication check in a layout so if you don't want to do it in the layout what are the other options you have to protect a page well if I want to protect the article page we just saw that I could do something like this right so this this is one option you have to do it directly in the page that you want to protect the other option you have is to do the authentication in middleware right so I can create a middleware dots file directly here as a child of the source and here I can export a function as a default just calling it middleware and this will take the incoming request and we can wrap that request with this middleware function from kind right so this middleware just will give you high level overview of our nextjs app right so in one nextjs app this is one nextjs app we have a client side as well as a server side so when there is a request right when we go to/ article or through some navigation like that there is essentially a request from client to the server side but before we even get to the server you may also want to invoke some middleware right so middleware sitting in between and the middleware can already check if the user should even be able to access that route and so we can already do the authentication check in there if the user is not authenticated we can already redirect no need to go all the way here this can be done much faster right so these are essentially the two main options you have to do authentication checks for a page right you can do it directly in the page component or in middleware now middleware by default actually runs on all routes so if I leave it like this it will actually also Run for the hom page which means that the home page right now also requires authentication so here I can actually also export a meter here to decide which routes should be and actually as a best practice I would argue that it's probably best to match all routes by default and you need to explicitly op out of them to make a page public right but here I'll keep it a little bit simple here we're just going to match it for SL article so right now the middleware would only protect SL article so let's actually compare authentication with middleware versus doing it directly in the page component like here which one is better this way or this way now out of these two I'm leaving more and more towards middleware these days I'm basically working on the rule of trying to use middleware unless middleware is not possible in that case do it directly in the page code component so let me actually explain why so first of all if you're doing these checks in in the page component it gets kind of messy right because I have to duplicate this for every page that I want to protect if I want to protect now the homepage I would have to copy and paste here and so if I want to know the pages that are public and private I have to open up a bunch of page files whereas in middleware I get a nice clean overview of all my private and public routes and that also means that if I have to change something now if I have to make a change here I potentially have to make sure I also do it in every other page that should be affected by that so I have to open up a bunch of page files it's just a very messy setup in my view whereas in middleware you're going to have one function essentially and I could have all of those check just in here it's just a much nicer overview also because in the middleware you may also have other redirect rules maybe based on the user's location and things like that so then you have all of your redirect rules essentially in one place so just in terms of organization I prefer middleware right so let me actually undo this we only want to protect the article page all right now I get another benefit by doing it in the middleware so let me actually show you that let me actually remove it from the pages so now I'm only protecting my app not by doing it directly in the page I'm doing it in the middleware only right what is the benefit of that well it has to do with static and dynamic rendering so let me actually run a build here right so here you can see it's doing something called generating static pages and we actually get an we actually get useful output here so when you run a build nextjs will let you know which routes or pages essentially are static and dynamic here we have this symbol it means static so we can see that the Home Route has been pre-rendered as static content so this homepage here we can already generate HTML during the build and so whenever somebody comes to our page we can just give them the HTML that we already created during the build and so it's going to be very fast because the HTML is already there waiting for anyone that wants to come to the homepage that's this home component doesn't have to reender it just has to be rendered once during the build and then we have the HTML we can put HTML on on a CDN and then whenever somebody comes we just serve it from the CDN right that's optimal that's the fastest right that's static rendering so that's what we want right because that's faster than the alternative which is dynamic rendering so if if the homepage was Dynamic it means that whenever there is a request we would have to render this again and run all the statements inside the function component here and and compute the output and send that to the user and then when the next user comes we're going to do it all over again that will take longer because it has to be rendered over and over again when whenever there's somebody coming right so ideally if it's possible we want a page or route to be statically rendered and that's the case here by default nexts also creates a not found Route here we can ignore that here here here we have the kind API endpoint we can ignore that here as well so here we have our article page and this right now is also statically rendered right so read article here if I go here it's statically rendered right so that's one of the benefits actually with middleware which is if you protect your routes with middleware your protected routes article this case can stay statically rendered uh I can still go here because I was I logged myself in from before but it is protected here and it's still statically rendered right that's what we want if I was doing the authentication check like this in the page component and now if I delete middleware here now I'm only doing the authentication check in the page component directly now if I run another build let's see what we got right so it's going to generate static Pages all right so now you can see homepage is still static because we're not doing anything there but now the article page which is dynamically rendered so whenever there's a request we're going to run all of these statements in here again compute the output and send that to the user and then when another user come we're going to do it all over again right so this is going to take longer so that's one downside of doing it directly in here you're going to turn this page into Dynamic rendering in xjs a page by default is actually statically rendered so by default as we saw initially it was all static it's only when you use certain features in nextjs like cookies for example or headers these functions here to read something from the incoming request that your page is automatically opted out of static rendering into Dynamic rendering and here in my cookies this is going to be sent with a request right so if you want to read an incoming token like that you need to use cookies but that can only happen during request time right so we cannot run this once during a build because it depends on the user that's currently making that request right so next CH will automatically make this route or page dynamically rendered well you may say we are not using cookies or headers here right so here we're not using cookies or headers so why is this is dynamically rendered like that well that's because if you're using a third party SDK they will use cookies or headers under the hood and they have to do that right because they have to check if that token is in your cookie we want them to do it right so when you do the authentication check in here you're going to turn your page into Dynamic rendering if this was a dashboard app we may not even care that much about static or dynamic rendering because everything is so personalized in the dashboard app but if you have a project where for example an article page this is not really personalized to the user this is just this is going to be the same essentially for everyone so if you have like a Content website like this where you want to have content behind a log in we actually want this to stay static right and the way we can do that is not by doing the authentication check in the page component we can keep it static by using middleware right so middleware can preserve static rendering all right now one side note here about static and dynamic rendering very often you also want to display some user information app wide right so maybe we want to have like a header across the entire project that showcases the user's Avatar or email right so let me just actually delete all of this I'm going to delete middleware we have no authentication now I just removed all the authentication from all pages no middleware but let's say we do want to have some header component that actually displays some user information right header so in the header we want to get the user who's logged in I can use kind here right all components are server components by default so I can use this one and this time I get the get user function which is actually a okay so then here I get my user now in the header you would have like a logo logo and then here maybe I want to show logged in with email and then just the user email let's say or Avatar right something like this this is a very typical component actually now I want to show this everywhere right so what you would do is you would go to your layout here so then in your layout here you would include the header right actually very common right so if I now import my header here I can see logo and logged in with email and then my email right so this is displayed on all Pages I'm still logged in from before that's why I see my email here all right now what's the problem with this well let me run one more build mpm run build okay generating static Pages all right so now what we have is actually everything is dynamically rendered how is this possible it's because this header is being displayed on every route and it depends on the currently logged in user right so that incoming request we need to see who's logged in right so that's why when you have an app wide component like a header and you're accessing user information in there you need to be aware of this the alternative here would be to use a client side hook actually right so kind on the client gives us this hook and it's actually very similar so instead of an async function it's just going to be like this so pretty much the same now we do need to make it a client component in that case so use client right so this way I can still get the user and display their email but now if I create a build one last time actually now actually the last time all right so now you can see I'm accessing user information but the pages are still statically rendered right so you want to be a little bit careful with how you structure your app with authentication right now I will delete it from here that's all I have to say about static and dynamic rendering for now right so let's actually go back to what we had before so now we have no authentication right so so far we've seen two arguments in favor of middleware is in my view it's a little bit better for organization but also better for keeping Pages statically rendered now another benefit of middleware is that this so if we take a look here so when we make the request all the way to some server we can have middleware already running closer to the user in different servers around the world that are closer to the user so we can make this authentication check even faster right so if the user is not authenticated we can immediately we can already redirect faster than if the request has to go all the way to our server component somewhere and then all the way back so with middleware it's just faster and so by default if you deploy to forel for example this middleware is going to run on the edge Network so this is simply faster now there is one important Point here which is that it will be faster if you're using uh Json web tokens right so here I'm using kind this is a j this is a Json web token so a Json web token will typically contain all the information that is necessary to make that off decision right so once there is a request from the browser to the middleware we don't need to make an external fetch call to verify if the user is actually logged in Json web tokens in combination with middleware is really good for performance but what if you're using database sessions right so in a database session setup as there is a request from the client to the server side we will still run the middleware the problem is that in that case you're not going to have the Json web token in your cookie you're going to have some ID in the cookie and that ID needs to be matched up with something that's in the database so then you have to make another fetch call to verify that the ID in the cookie matches with some session in the database and database sessions and so you have to connect to your database and that simply may not work in middleware a lot of these database drivers or or cannot run properly in middleware because in that edge Network it's a limited environment so that may not even be possible right so here to do the authentication check in middleware this may not be possible if you're using database sessions so in that case you kind of have to stick to doing it in the page component but if you're using Json web tokens which I'm using here I don't have to make this whole database call run all of that here in the middleware so this should give me a performance benefit so that would be another benefit of the middleware in case you're using Json web tokens but if you're using database sessions it may not even be possible to use middleware for that right so basically my conclusion is I I try to use a middleware unless it's not possible because maybe I'm using database sessions and I omm or my database driver cannot connect like that in the edge runtime in that case I would be more inclined to use the authentication check in the page component because I don't really have a choice so here we have the essentially the data that should be private right so here it's just text could be any kind of data and I kind of just hardcoded it here in this page but you can imagine that we actually have a separate component for that so here we may actually have an actual text. TSX component right so we may actually have the data in another component we may fetch the data in this component the point being is it may not be actually hardcoded here on the page it may it may sit in a component here let me actually add these Imports back again as well so no matter what option you pick whether you're going to do it in the page or with middleware you will still have some risk here let's say we do it in a page now you still have this issue where your data may be in some nested component right so the data may be in text here we may even be getting it from our database here but we're doing the check here up here now the risk with this is that well we are making components reusable that's front of mind for every react developer make your components reusable now actually very often we don't reuse components but sometimes you do so it's possible that maybe you or some other developer on your team will actually reuse this components right this text component maybe somebody will actually use this on let's say the homepage right so here I have my homepage maybe somebody includes it here on the homepage which is a public page we are not protecting it here right so now everybody can go to the homepage and can read this text even though I added protection here so that does not protect against essentially reusing it somewhere else so the reason that this risk exists is because there is a gap between where you do the authentication check and where you actually access or use the data right so you want to do the authentication check as close as possible where you actually access the data right so actually what we may want to do instead not just here in the page we want to do it directly here in the text component because this is as close as possible to the actual secret data this is a server component so we can just do that so now if I use text somewhere so if I would use it here or accidentally perhaps on a public route like this well that's not really a big problem because here we are doing the authentication check anyway right so really important I think is you want to keep the authentication check as close as possible to your data now we may still want to have a page level check as well in here in the article page and we will have the exact same problem if we're using middleware right so if I wasn't doing this but instead I was using middleware I would have the exact same risk right so now I'm not doing it here in the article page I'm doing the authentication check here with middleware middleware runs only for slash article here so if I have my data in this component and I use it on some page that is not/ article so on the homepage for example right so what I'm doing here the middleware will not protect against that right so how do we protect ourselves against it the reason that this risk exists is because there is a gap between where we do the authentication check and where we actually access or work with the data right now you can still have a page level authentication check in addition to that right so you could actually still keep this or if you're using middleware you can still keep middleware of course but then also in addition to your page level or middleware uh check have another check essentially right before you access the data last thing I want to show you actually has to do with server action all right so let's say we have another page the edit article page right so we want to have a page where maybe some user can edit an article right so here we have a simple form right very simple we just have this form here with we just have the form right here we have the input and then we have a submit button now here I'm using a server action right so I can use the action attribute with essentially a server function and these are a little bit confusing for some people but they they are different from server components right server component is a react component a server action well something like this right so this is a function that runs only on the server and so we use the U server directive for that so now when I submit the form next JS will make sure that the data from the form is actually submitted from the browser to the server side right so let me actually prove that to you if I submit this right now right this form this action will be invoked right so we're going to get the form data here and I'm logging this here it's essentially a function that runs only on the server so if you're logging something I need to open up my terminal here you're not going to to see it in the browser terminal right that's for the client side so if I just say test here if I say test and submit you can see here I'm logging it on the server side now how is it possible that it goes from my browser to the server side well if I do it again if I say test two and press submit you can see there is actually still a fetch call from the browser to the server side but that's done for me automatically right so you can see there is actually a post fetch call to some URL and actually it's the same URL as the one on which you're using the server action so that would be edit article in this case and so you're essentially opening up an endpoint on your server and a post endpoint and it may be technically possible that somebody else will make a request to there right somebody could just try making a post request to this address right so what you want to do in a server action is when somebody submits a new title we would want to update the database right but it's technically perhaps possible that somebody else can make a request and invoke that server action just like your traditional API endpoints and Route Handler you want to make sure sure that the user is authenticated now you may say well this server action lives on the same location essentially as edit article so if we just protect edit article right just like what we've been doing maybe with for example middleware right so if I say that the meter should also run on should actually be an array on edit article and let me actually log here so you can see when middleware is running middleware running okay when middleware runs we are doing authentication right so now if I submit this right so here if I want to log that it's actually also a server sign so I need to open that up here so now if I submit here I submit you can see middleware is running so it seems like the server action is properly protected right because middleware will be running even for the server action because it kind of lives on the same page on which you're using it and here we're protecting edit article but we still have the risk that we use this server action on a public route maybe for some reason we or somebody else uses this function on the homepage let's say right so here we have a homepage I can actually have this the exact same form on the homepage and invoke the server action here on the homepage if I go to homepage here and here I have my form now and so if I just refresh here A bunch of times so we can clear the terminal here so now if I say test five if I now press submit here you can see we are logging it but we do not see middleware running so the middleware is not protecting against perhaps accidental usage of the server action on a public page and this is actually very similar as to accidentally using a reusable component on a public page and the reason that the risk exists is because again there a gap between where we actually access our database and where we do the authentication check right so here I would do the authentication check actually in the server action itself and so here you would do an authentication check just like you would do anywhere else on the server side so with kind you just get one function for everywhere on the server side and then you can do whatever you want with your data right so just to conclude here what I would do if I were building a professional robust nextjs application is I would use middleware for like the first line of the fence now if that's not possible maybe it's because I'm using database sessions I would still have an a check in the page component knowing that this will make it dynamically rendered but most of the time that's not really a problem so I would accept in that case that it would be dynamically rendered so that would be your first line of defense essentially so either middleware or some check in the page component and then in addition to that because as we saw it's not enough right so you could accidentally access data on a public route right that's not covered by this check or by middleware so to protect against that risk I would have an additional check at the place where I'm actually accessing data what we're discussing here is essentially The Cutting Edge of nextjs right now so if you're a little bit confused I think that's totally normal now the good news is we don't have to worry about managing tokens and all that other good authentication stuff because we can use kind so I want to thank kind for sponsoring this video I would say check them out and I want to thank you for watching the video hopefully it was helpful and then I hope to see you the next one bye
Info
Channel: ByteGrad
Views: 25,246
Rating: undefined out of 5
Keywords:
Id: kbCzZzXTjuw
Channel Id: undefined
Length: 26min 20sec (1580 seconds)
Published: Mon May 13 2024
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.