The Right Way to do Auth with the Next.js App Router - UPDATED

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
<b>I'm going to show you how to properly add</b> <b>cookie-based</b> <b>authentication to an existing</b> <b>Next.js app. We're going to use this</b> <b>email client example that we've been</b> <b>working on for a few</b> <b>videos. There's a link to the full</b> <b>playlist in the description,</b> <b>but for this video we're going</b> <b>to focus on all things authentication,</b> <b>why you need cookies, and how</b> <b>you can use the new Supabase</b> <b>SSR package to handle all the tricky</b> <b>bits. This is an updated version of this</b> <b>video because I said</b> <b>to do something that you probably</b> <b>shouldn't do with the Next.js app router,</b> <b>but more on that later.</b> <b>Let's get into it. So here I have our</b> <b>email client. It has folders</b> <b>on the left, a list of emails in</b> <b>that folder, and then if we click on one</b> <b>of these emails we can see</b> <b>its contents. But it doesn't</b> <b>really make sense for us to see anything</b> <b>on this page if the user is</b> <b>not currently signed in. So</b> <b>if we click this login button we'll see</b> <b>our authentication page</b> <b>and we have this button</b> <b>to log in with GitHub. The code for this</b> <b>page looks something like</b> <b>this. We have a form with</b> <b>a submit button and the GitHub logo. When</b> <b>we click it that submits</b> <b>this form to this sign-in action</b> <b>which is declared above. So this is a</b> <b>server action and right now</b> <b>we're just console logging</b> <b>out, sign-in with GitHub. And if we open</b> <b>up the console for our dev</b> <b>server we can see that message</b> <b>from when we clicked earlier. So we can</b> <b>get rid of that console log</b> <b>now and we just need to create</b> <b>a new Supabase client. So we can do that</b> <b>by saying const</b> <b>supabase is equal to calling the</b> <b>create client function which comes in</b> <b>from ./utils/supabase</b> <b>And if we have a</b> <b>look at that one it's just wrapping a</b> <b>call to the create client function that</b> <b>comes in from Supabase-js</b> <b>just so we don't need to copy and paste</b> <b>these environment variables</b> <b>or any other configuration</b> <b>that we might add as part of this video.</b> <b>So if we go back to our</b> <b>login page we now have our</b> <b>Supabase client and so to sign in with</b> <b>GitHub we can await a call to</b> <b>supabase.auth.signinwithOAuth</b> <b>and this takes a configuration</b> <b>object with the key</b> <b>provider set to the string GitHub.</b> <b>Now I configured GitHub as an</b> <b>authentication provider in between these</b> <b>videos so if you need</b> <b>to do that for your project check out the</b> <b>guide linked in the</b> <b>description for setting up an OAuth</b> <b>app with GitHub. So when we call this</b> <b>sign in with OAuth method it</b> <b>will give us back a couple of</b> <b>things. It will give us back an error if</b> <b>something went wrong or the data if</b> <b>everything was all good.</b> <b>So if we do get back an error then we</b> <b>probably want to be kind to</b> <b>our future selves and console</b> <b>log it out. Otherwise we want to return a</b> <b>redirect which is a function</b> <b>that comes in from next/navigation</b> <b>and then we just need to give</b> <b>this one a path that we want</b> <b>to redirect to. So in this case</b> <b>that will be data.url. So this is a URL</b> <b>that we get back from</b> <b>initiating this sign in with OAuth</b> <b>method. So this will send us off to</b> <b>GitHub to authenticate but</b> <b>then after that's finished</b> <b>we still need to redirect the user back</b> <b>to our landing page and</b> <b>so we do that by passing an</b> <b>additional parameter for options which is</b> <b>set to an object with the</b> <b>key redirect to set to our</b> <b>origin. And so we can get our origin</b> <b>above by calling the</b> <b>headers function which should come</b> <b>in from next/headers so we can just</b> <b>import that manually. So we</b> <b>want to say import headers</b> <b>from next/headers and then calling</b> <b>this headers function will</b> <b>give us back a list of all</b> <b>of the headers. So we want to call get</b> <b>and pass it the string origin</b> <b>and this is complaining that</b> <b>null is not assignable to type string or</b> <b>undefined. So we can just make this a</b> <b>string by using back</b> <b>ticks and then dollar sign open curly and</b> <b>then closed curly after it</b> <b>and back ticks. Don't worry</b> <b>we need to do this for the next step</b> <b>anyway. So when we click</b> <b>that GitHub logo we call this</b> <b>sign in with OAuth function which gives</b> <b>us back a URL which we</b> <b>redirect to to kick off that</b> <b>authentication flow with GitHub and then</b> <b>when that completes we want</b> <b>to redirect to the landing page</b> <b>and now back in our application if we</b> <b>click here to sign in with GitHub it'll</b> <b>step us through that</b> <b>OAuth flow with GitHub and eventually</b> <b>we'll end up back on the</b> <b>landing page of our application.</b> <b>Okay cool now that we have authentication</b> <b>let's make the rest of our</b> <b>app protected so you need</b> <b>to be signed in to be able to see any of</b> <b>those emails. If we have a</b> <b>look at our next.config.js</b> <b>file we'll see this has a redirect set up</b> <b>from the landing page of</b> <b>our application to slash f</b> <b>slash inbox so that's going to take us to</b> <b>this f route and then this</b> <b>is a dynamic route so this</b> <b>part of the URL will come in as this name</b> <b>variable and this</b> <b>page.tsx server component will</b> <b>be mounted for that route so we want this</b> <b>one to require</b> <b>authentication and we also want this new</b> <b>route to require authentication so since</b> <b>these both exist within the same sub</b> <b>route of slash f slash</b> <b>whatever the folder name is we can create</b> <b>a new file for layout.tsx</b> <b>which will be loaded for any</b> <b>routes within this folder so this server</b> <b>component and also this server</b> <b>component so this is a pretty</b> <b>simple layout component it takes some</b> <b>children as a prop and then</b> <b>returns those children again</b> <b>so we're not rendering anything</b> <b>additional but any logic we add before</b> <b>this return statement</b> <b>is going to be run for any of the child</b> <b>routes of this component so</b> <b>let's create a new superbase</b> <b>client by calling the create client</b> <b>function again that comes in from our</b> <b>utils folder we then want</b> <b>to check whether we have an authenticated</b> <b>user so we can say const the</b> <b>data that comes back and we</b> <b>want to destructure the user object we</b> <b>get this by awaiting a call to</b> <b>supabase.auth.getUser and</b> <b>so this user object will either be a user</b> <b>or null so if we don't have a</b> <b>user then we want to redirect</b> <b>which again is a function that comes in</b> <b>from next slash navigation</b> <b>and the path we want to navigate</b> <b>unauthenticated users to is slash login</b> <b>otherwise if we do have a</b> <b>user then we just continue on to</b> <b>render the children so let's step through</b> <b>this process in our</b> <b>application so we click the sign</b> <b>in with github button and it doesn't work</b> <b>hang on what's going on</b> <b>here are we not redirecting</b> <b>correctly to the landing page well let's</b> <b>add the best damn debugging</b> <b>tool there is out there the</b> <b>console.log here statement and let's go</b> <b>back to our application and</b> <b>sign in with github again we</b> <b>can see something's going on that little</b> <b>bar is pulsing when we</b> <b>click this so let's open up the</b> <b>console of our development server and we</b> <b>can see here here here so we</b> <b>definitely got here so let's</b> <b>move this under our get user statement</b> <b>and check what is our user set to and</b> <b>again let's click the</b> <b>button and open up our console and we see</b> <b>user is null okay you got</b> <b>me i knew this was going to</b> <b>happen but i wanted you to experience it</b> <b>with me the problem is that</b> <b>by default superbase.js stores</b> <b>the user session in local storage so we</b> <b>can see it is getting set</b> <b>correctly but local storage</b> <b>doesn't exist on the server and guess</b> <b>where server components</b> <b>and server actions run</b> <b>on the server therefore we need to use</b> <b>cookies to store the user</b> <b>session rather than local storage</b> <b>thankfully we've developed a super handy</b> <b>package called ssr for this</b> <b>exact problem let's use pnpm</b> <b>to install or i the at superbase slash</b> <b>ssr package we also need at</b> <b>superbase slash superbase.js</b> <b>but since we already have this installed</b> <b>in our project we just need the ssr</b> <b>package and now that</b> <b>that's done we can run our development</b> <b>server again and then open</b> <b>up this helper function for</b> <b>create client and we want to change this</b> <b>import to the create server client</b> <b>function which means</b> <b>we can get rid of this alias and this one</b> <b>comes in from the ssr</b> <b>package we can then copy and paste</b> <b>this one and then this is lit up red</b> <b>because we need to pass these two</b> <b>environment variables but</b> <b>then we also need an additional</b> <b>configuration object called options so we</b> <b>can pass that one so</b> <b>i'm just going to paste this one in and</b> <b>this is just declaring how</b> <b>to get set and remove a cookie</b> <b>in nextjs so we need an instance of our</b> <b>cookie store so we can</b> <b>declare that above which means</b> <b>we need to refactor this slightly to</b> <b>actually have a body of our function and</b> <b>then we want to return</b> <b>this big create server client blob so if</b> <b>we paste that one here then</b> <b>above that return statement we</b> <b>can declare a new variable for cookie</b> <b>store which is the result of</b> <b>calling the cookies function</b> <b>that comes in from next slash headers and</b> <b>so this cookie store can</b> <b>now be used to interact</b> <b>with the cookie headers on our request so</b> <b>we can call cookie store dot</b> <b>get pass it a name and then</b> <b>if we get a cookie back then we want to</b> <b>return its value and then we</b> <b>also declare how to set and also</b> <b>remove a cookie we also need to import</b> <b>this cookie options type which it's not</b> <b>nicely inferring but</b> <b>this comes in as a type from the ssr</b> <b>package so we can say we want the create</b> <b>server client function</b> <b>and also the type for cookie options and</b> <b>so this adds quite a bit to the</b> <b>configuration when we're</b> <b>creating our superbase client but</b> <b>anywhere we're calling this</b> <b>create client function can remain</b> <b>exactly as it is as well as how we're</b> <b>using that client to do</b> <b>things like get our user or over in</b> <b>our db actions dot ts file where we're</b> <b>using our superbase client</b> <b>to perform mutations or in our</b> <b>queries dot ts file that has all of our</b> <b>data fetching logic thankfully we also</b> <b>have this awesome nextjs</b> <b>auth guide which will be linked in the</b> <b>description that steps through all of</b> <b>this with some nice copy</b> <b>and pastable code snippets so you don't</b> <b>need to pause the video and type out</b> <b>every single character</b> <b>sorry if you already did that there's</b> <b>also a sneaky way to</b> <b>generate all of this so you don't</b> <b>have to type any of it but we'll talk</b> <b>about that at the end of the</b> <b>video there are just two more</b> <b>bits we need to configure the first one</b> <b>is in our login page after we've</b> <b>authenticated with github</b> <b>we want to redirect to our origin so this</b> <b>will either be localhost</b> <b>over port 3000 or whatever</b> <b>our production url is but then we want to</b> <b>navigate to slash auth slash</b> <b>callback and then we need to</b> <b>declare this new route so under the app</b> <b>folder let's create a new</b> <b>file at auth slash callback</b> <b>slash route dot ts so this will be a</b> <b>route handler and again i'm just gonna</b> <b>paste in this big blob</b> <b>so this is listening to get requests on</b> <b>this route it's then getting the code</b> <b>from the search params</b> <b>which will be automatically sent to this</b> <b>route by github during that</b> <b>authentication flow so then</b> <b>we're creating a superbase client to</b> <b>exchange that unique code for the user's</b> <b>session and then we're</b> <b>just redirecting the user again back to</b> <b>that origin so this will</b> <b>just navigate back to the</b> <b>landing page so this pattern is called</b> <b>the proof key for code</b> <b>exchange pattern or pixie which is</b> <b>just a more secure way of doing oauth so</b> <b>when github is happy that</b> <b>our user is who they say</b> <b>they are it redirects them to our slash</b> <b>auth slash callback route with this</b> <b>unique code which we</b> <b>exchange for the user's session and so</b> <b>all of that redirection stuff happens</b> <b>automatically by telling</b> <b>github where to redirect the user to</b> <b>after completing that</b> <b>authentication process and we</b> <b>can test this whole authentication flow</b> <b>in the browser by going to the</b> <b>authentication page and</b> <b>clicking sign in with github and you can</b> <b>see we've been successfully</b> <b>redirected to the landing page</b> <b>and we can see my github avatar and also</b> <b>my github username and if</b> <b>we open up the console and go</b> <b>over to application we can see our</b> <b>superbase session has been</b> <b>correctly set under cookies so</b> <b>our user is now authenticated but there's</b> <b>a bug when the session expires and</b> <b>superbase attempts to</b> <b>refresh a new sesh the user will be</b> <b>unexpectedly signed out and if we have a</b> <b>look under application</b> <b>and cookies they're gone where are my</b> <b>cookies the reason this is</b> <b>happening is because server</b> <b>components only get read access to</b> <b>cookies so when we call</b> <b>superbase dot auth dot get user</b> <b>this will automatically refresh expired</b> <b>sessions before returning</b> <b>that user object but because</b> <b>this layout is a server component it</b> <b>can't update that cookie with our new</b> <b>session and so the browser</b> <b>just automatically removes the expired</b> <b>one therefore we need to</b> <b>use a route handler server</b> <b>action or middleware to refresh this</b> <b>session before it hits our server</b> <b>component route so let's create</b> <b>another helpful little helper under utils</b> <b>slash superbase so this is</b> <b>going to be a new file called</b> <b>middleware dot t s and this is going to</b> <b>export out this big scary</b> <b>blob no it's not really that</b> <b>scary it's just a quite verbose way of</b> <b>again getting setting and</b> <b>then removing a cookie the</b> <b>difference is in middleware we need to</b> <b>change the cookies on both</b> <b>the request and the response so</b> <b>it looks just a little bit more</b> <b>intimidating again you can get that one</b> <b>from the docs linked</b> <b>in the description or just wait just a</b> <b>little bit longer and i'll</b> <b>show you that single command that</b> <b>does all of this for you so now we just</b> <b>need our actual middleware dot t s file</b> <b>so we want to create</b> <b>that at the rootmost point of our project</b> <b>so again middleware dot t s</b> <b>and we're going to paste this</b> <b>one in here so this one uses our</b> <b>middleware helper to create a superbase</b> <b>client it passes in the</b> <b>request so we can modify the cookies on</b> <b>that request and gives us</b> <b>back a modified response</b> <b>and also our superbase client then we're</b> <b>calling superbase dot auth</b> <b>dot get user and we're not</b> <b>actually doing anything with the user</b> <b>we're just throwing it away but again</b> <b>this get user function</b> <b>attempts to refresh expired sessions and</b> <b>then because middleware</b> <b>has read and write access to</b> <b>those cookies it can update the cookies</b> <b>on the request and the</b> <b>response before returning this</b> <b>response which will load our server</b> <b>component route that the user</b> <b>requested so again this just</b> <b>ensures that we have a fresh session</b> <b>before we reach the server</b> <b>component because the server</b> <b>component only has read access to these</b> <b>cookies to determine whether</b> <b>this user is authenticated or</b> <b>not and the last little part of this</b> <b>middleware file is exporting a config</b> <b>containing a matcher</b> <b>and so this is a big scary regex or reg</b> <b>ex pattern that excludes a</b> <b>bunch of stuff so static files</b> <b>favicons all these different image types</b> <b>things that we don't want</b> <b>to run this middleware logic</b> <b>because they're just assets for our</b> <b>application rather than server</b> <b>component routes which is all</b> <b>we really need to run this for and now</b> <b>our user can go on happily using this</b> <b>application and reading</b> <b>all of their unread emails and when their</b> <b>session is ready to</b> <b>expire it will be automatically</b> <b>refreshed in the background as they're</b> <b>going through using the</b> <b>application and right here</b> <b>is where i made a little bit of a boo boo</b> <b>in the previous version of</b> <b>this video and how i've been</b> <b>building apps generally with the nextjs</b> <b>app router turns out using a</b> <b>layout for protecting pages</b> <b>is not a safe or reliable way to do it</b> <b>you can click that card</b> <b>above if you want to go deeper on</b> <b>why that's the case but the recommended</b> <b>path from the nextjs team and the most</b> <b>performant and secure</b> <b>option for protecting pages is to do</b> <b>these checks in middleware so</b> <b>let's take this logic from our</b> <b>layout.tsx file where we're getting a</b> <b>user from superbase and then</b> <b>if there isn't a user we're</b> <b>redirecting and let's instead put that in</b> <b>our middleware.ts file so</b> <b>we're already doing part</b> <b>of this logic where we're getting the</b> <b>user from superbase but</b> <b>instead of just throwing that user</b> <b>away we now want that user object and if</b> <b>there is no user then we</b> <b>want to return a next response</b> <b>dot redirect and next response comes in</b> <b>from next slash server and</b> <b>then we just need to refactor</b> <b>this redirect slightly to pass it a new</b> <b>url of that slash login</b> <b>path and then as a second</b> <b>parameter the request dot url and we can</b> <b>now delete this layout.tsx so</b> <b>if we open up our project make</b> <b>sure that's the one under f slash name</b> <b>and not our rootmost layout so let's</b> <b>delete this one and now</b> <b>we have our middleware protecting all of</b> <b>our pages and making</b> <b>sure we have a user before</b> <b>returning a response but since middleware</b> <b>runs on every single route</b> <b>and the login page is one of</b> <b>those routes we end up in an infinite</b> <b>loop so we just need to</b> <b>check if we don't have a user and</b> <b>the current route is not the login page</b> <b>which we can get by saying</b> <b>request dot next url dot path</b> <b>name dot starts with and then the path</b> <b>slash login then we want</b> <b>to redirect the user to the</b> <b>login page so if we have a user then</b> <b>we'll just return the</b> <b>response or render the page they're</b> <b>looking for if we don't have a user and</b> <b>we're trying to load the</b> <b>route slash f slash inbox</b> <b>for example then that does not start with</b> <b>login so we redirect to the</b> <b>login page if we don't have</b> <b>a user but we are navigating to the slash</b> <b>login page then we skip</b> <b>this redirect and return the</b> <b>response which happens to be the login</b> <b>page but there's no infinite</b> <b>loop but that's quite a bit</b> <b>of manual configuration if you want to</b> <b>skip all of that i recommend you check</b> <b>out this video right</b> <b>here we use a special super base template</b> <b>that's built right into the</b> <b>create next app cli tool to</b> <b>configure all of this server-side cookie</b> <b>stuff so you don't even need</b> <b>to think about it but until</b> <b>next time keep building cool stuff</b>
Info
Channel: Supabase
Views: 10,484
Rating: undefined out of 5
Keywords: supabase tutorial, best firebase alternative, open source database, superbase, superbase app
Id: v6UvgfSIjQ0
Channel Id: undefined
Length: 15min 56sec (956 seconds)
Published: Fri May 10 2024
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.