Overview and project setup
Let's talk about authentication in Next.js. This is a broad topic and there's a lot that we could talk about. So in this video I'm going to cover showing how to do basic session authentication in Next.js without any additional libraries and then I'll also show some other options you can use too if you don't want to roll your own off. So let's get into it. The code for this video will be in the description and we've also published new docs for both the pages and the app router so go check those out. But what I've done is I've cloned this application locally, I've got it up in my editor and we're just going to talk through some of the bits here. So first we have our root layout which hasn't changed from setting up a Hello World Next.js app. This is just the HTML in the body of our application and then I have our index route so I have our page file denoting that route.
Index route and page component
This is a server component so we've marked it as async and we're awaiting getting the session for our application. If there's a user we're going to print out information about the user here; otherwise we're just going to say it's null. And then we have two different forms here. We have one form that allows the user to log in and it calls the server action. It calls this login function and then redirects back, and then we also have the same thing basically to log out. So this is just a basic scaffolding of login, log out. There's of course a lot more that we can do here, but the real meat of understanding the basics of authentication is going to be in the login and log out functions. So we've moved all of this library, everything related to authentication, into this one lib.ts file. Let's start with login.
Login function and creating a session cookie
The login function that gets called over here. I'll just type in leel.com, hit enter to submit our form, and we see this information that is logged out in the right. So this login function is called. It takes in the form data. I can read information from the form like the email. I can put some other additional information here on the user object. In your application this is where you would probably talk to a database and look up information about the user. And once we get that information back then we get to create our session. So we're going to define when we want it to expire through a new Date. We're going to make this session option or session object which is going to be encrypted and it takes the user and the time that it expires. And then we set this as a cookie. So Next.js has this cookies function that allows you to set and delete cookies. So we set this with the name of session, we pass along the session, we say when it expires, and critically we say this is an HTTP only cookie so you can only read this on the server. Now we skipped over this encryption part but this is really important.
Encrypting the JWT and inspecting it in the browser
If we go up to encrypt you'll see that we're doing a couple things here. This is an asynchronous function. It takes in the payload for our JWT, or JSON Web Token, and we're going to call this function from this library here, jose, which is going to, or which is going to allow us to sign the JWT based on the payload and the algorithm that we want, when it was issued, when we wanted to expire at, and then the secret key that we're going to use for that encryption. Now here I've just defined it up top. In your application you probably want this to be an environment variable or something that people wouldn't have access to, so definitely don't want this value to get exposed. So we take this; this is what's actually going to encrypt that JWT. So let's take a look at what that looks like in my browser. I'm going to open up dev tools and I'm going to reload the page. There's no session right now because it had expired. So I'll do leel.com, I'll hit log in and we set this session key here. So let's take this value, this encrypted JWT, and let's pop on over to jwt.io. So I've pasted this in here and we see on the right it determines what the algorithm was. It shows the payload of the data and then it says, hey if you want to verify the signature you need to put in that secret bit. So this is just a nice way of being able to kind of visualize that data, just a helpful little tip. So the session is stored as a cookie here and we said it expired in 10 seconds. So when I reload the page it's expired; that cookie is no longer there, it's done. If I log in again—leel.com—and I reload, what you're going to notice is that the value of expires is getting updated every single time and you would also see that reflected in the decrypted value of the JWT that we're storing.
Middleware: refreshing sessions, decrypting, and reading session
How is this happening and why are we doing this? Well if we pop back over to our application code and we look at the middleware file, this file is going to run in front of every request in our application and it's calling this function. It's taking in the web request and it's calling updateSession with that web request. So let's pop back into our authentication file where we've been kind of building our own off library and we have updateSession. So it takes in that request. NextRequest is just an extension of the web request. It takes in that request, it looks at the cookies. If there's no session, if there's no session cookie provided, you can just return back. Otherwise refresh that session so it doesn't expire. So we decrypt that value from the session, we set a new expiration time, we tell NextResponse that we're going to produce a response, a web response from this, and then on that response we set a new cookie. Now the bit that we skipped over here was the decrypt function. So let's go take a look at that. Decrypt takes in this input. It uses jose as well to verify based on the input, based on the key, based on the algorithm that we encrypted it with. If this is legit, we're going to decrypt it and return back the payload. So that happened down here where we got back the value from decrypt and then we were able to update the cookie. To round out our off implementation we need to read information about the request. So we talked about this getSession function when we first looked at the page, but what exactly was this doing? Well really it's just reading from the cookies. So we already showed how we were setting the JWT as a cookie for our session. All this function is doing is just reading from the cookies, looking for that session value, and then decrypting the value. So that's how inside of here—again if I go back to the browser, I do leel.com—this could look at my database or something. That's how this getSession function is able to get back information about the session. Now if I click log out with the server action, what's going to happen? If you watch my console over on the right side you see that the cookie was deleted. So it was removed out of my cookies. And if we click into the logout function all this was doing was calling the Next.js cookies function and it was essentially destroying the session. So this file is really all it takes for the most minimal session-based authentication inside of a Next.js application. You're reading your values inside of your page, you are using a middleware to refresh that information, and it of course can get way more detailed than this, but I think it's helpful to step from just a really basic example and we can add complexity on top from there.
NextAuth abstraction, providers, and closing thoughts
Now I want to show what an abstraction on this model looks like. So the basic model I showed is effectively a very light version of NextAuth, which is a popular community library, and I want to walk through another example that uses the same setup but using NextAuth. So I've got that example open here on the left and I've got it running in my browser on the right and we're going to see a lot of similar things, just one layer of abstraction higher. So I have this auth file, auth.ts. It's going to set up some sign in, sign out methods and also some route handlers for our application and it's going to say that we're using the GitHub provider for Auth. Those route handlers are under api/auth/[...nextauth].ts. So this catchall route is going to scaffold a bunch of those route handlers for us for the library to use. So we take the get and the post from that, you know, central NextAuth setup and we scaffold out these routes and then back in Auth we don't really have to do anything else to get this working with GitHub. But since this is again a layer of abstraction higher, this works with a bunch of different auth providers. It could be GitHub, Google, Discord, really any provider you want to use. So let's go look at the index route and the page. For our page we are calling await getSession which is basically similar to getting the session. This was the value that was exported from our auth file. From the session we get the user and the email and then we render out this information below. So I click sign in with GitHub. I've already configured and authenticated to say yes I authorized GitHub so it skipped the modal, but I say okay, welcome, my email address here. I can click sign out. And when I look at these other components they're very similar: they're forms that have a server action, they call a signIn function or a signOut function. The signIn one takes in a string which is the provider that I'm using; in this instance GitHub. And signOut just deletes the session basically. So for example if I open up the cookies—this is for 3001 localhost running on a different port—making this a bit bigger we can see there were three cookies created. There's the session token which is that encrypted JWT, there's the callback URL, and then there's also a CSRF token for increased security. So this is all abstracted away for us. We don't need to do anything here. Just like in the previous example there's a middleware so that the session can be refreshed when you reload the page. This one also includes a config option with the regex for what routes we want this to run on, so it's not going to run the middleware on static files or _next/image or on the favicon. So that's probably good to also include as well. It's worth noting that depending on what time you're watching this video these changes might already be stable. I'm using the latest beta version of the NextAuth package that's been totally refactored for the App Router to really simplify things. So the code for this is down below if you want to get started on this version; it might already be stable by the time you watch it though, so just wanted to quickly mention that too. The topic of authentication goes super deep. There's not only authentication, there's also authorization. So we cover some of this in our new docs; we talk a little bit about authorization and protecting client actions, protecting route handlers. In this video we talked about cookie-based sessions but there's also database sessions and you know there's really a ton of stuff here. We also include a bunch of examples with other popular authentication libraries whether it's using Clerk or Lucia or Supabase or any of these here. Worth checking out. If you'd like to see us talk more about authentication definitely let me know in the comments what you want to see with Next.js and NextAuth or Auth.js. We can go in more deep context here. But hopefully this was a good introduction into just a very basic cookie-based session example. Let me know what you thought. Peace.