I explored Authentication in Next.js 15 using Nextâs App Router, React Server Components (RSC), and Server Actions. This comprehensive tutorial covers hand rolling your own authentication for sign up, sign in, sign out, and protected routes.
If you want to go beyond this with the implementation of password change, password reset, forgot password, email verification, organizations, roles, permissions and memberships, check out âThe Road to Nextâ .
We will only use React Server Components with Server Actions and no Client Components at all for this authentication tutorial. In other words: We will not use any client-side JavaScript for the authentication flow. Instead, we will use React Server Components and Server Actions to handle the authentication logic on the server.
Weâll start with a fresh Next.js application and add authentication to it step by step. Letâs adjust the root page that we get with a new Next.js installation:
The wording already suggests that we will have a public home page which will be accessible without authentication. We will add a dashboard page in the next step which will later be protected and only accessible when the user is authenticated. If a user is not authenticated, we will implement a redirect to a public page:
Whatâs missing is the navigation between both pages. We will use the root layout in Next.js which is the perfect place for a navigation in a header (which you could later extract as a standalone React Server Component):
This layout will be shared across all pages for enabling users to navigate between them. We will later add routes for sign up, sign in, and sign out to the navigation. They will conditionally show up, whether as user is logged in or not. In addition, the sign-out button will only be accessible when the user is authenticated.
Next we will also add a sign-up page and a sign-in page to the application which will be accessible without authentication. The sign-out button will be later only accessible when the user is authenticated. First the sign-up page:
And second the sign-in page:
We will add both pages to the navigation in addition to the sign-out button:
Thatâs it for all the pages needed for a basic authentication flow. Later you may want to add more pages for password forgot, password reset, email verification, and so on. But for now, we will focus on the basic sign up, sign in, and sign out flow.
We will start with the sign-up flow in this step. There we will use a new SignUpForm component on the sign-up page which we will implement in the next step in the feature folder for everything related to authentication:
The sign-up form will be a simple form with a few input fields and a submit button. Not all of the fields are necessary for a sign-up, because having an email and password should be sufficient, however, we will add a few more fields for the sake of completion.
We have used a Server Action in this React Server Component in the formâs action prop for the form submission. Natively in Next.js with Sever Actions the action will be a function that takes the form data and handles the sign-up logic.
Because we wonât store the actual password in the database, we will hash the password before. You can verify this later with Prisma Studio. We will also create a user in the database (and would also have to handle the error if a user with this email already exists):
Furthermore we create a Session in the database with the createSession auth function and set a session cookie for the browser the setSessionCookie auth function. With Nextâs built-in cookies API, we can set the session cookie within the setSessionCookie function. Last we redirect the user to the dashboard page.
After you performed your first sign up in the application, you can check the database with Prisma Studio. You should see a new User and a new Session in the database.
You can check out my other tutorial on form validation in Next.js with Zod for further information about form validation with fine-grained form field errors, toast messages, and form resets.
We will add a sign-in form to the sign-in page in the next step. The sign-in form will be a simple form with an email and password input field and a submit button. We will also use a Server Action in the formâs action prop for the form submission:
Notice how we havenât used any Client Components in this tutorial. We have only used React Server Components and Server Actions for the authentication flow. And we will keep it this way:
Lastly, we will implement the sign-in logic in the Server Action. We will use the email to find the user in the database and then verify the password with the hashed password in the database. If the password is correct, we will create a new session for the user and set a new session cookie in the browser:
Perhaps it is difficult to test the sign-in logic, because we already have a valid session in the database from the previous sign-up flow. With Prisma Studio, you could delete the session in the database and then try to sign in again. Then you should see a new session in the database after you have signed in.
Next we will add a sign-out button to the Root Layout (in the nav element). In a React Server Component, we cannot add a handler to the sign-out button, because we would have to have a Client Component for this case:
Therefore we would have to extract the sign-out logic to a Client Component. However, we can use a trick to call it in a React Server Component by using a form button :
And import the a new action that we will define in the next step
We will implement the sign-out logic in the already provided Server Action. We will invalidate the session in the database and create a new session cookie with a blank value that expires immediately. Last we will redirect the user to the sign-in page:
Check the database with Prisma Studio after you have signed out. You should see that the session got removed in the database. Your authentication flow should be complete now. You can sign up, sign in, and sign out in the application. You can also check the authentication status of the user in the application which we will prove in the next step.
With a authentication flow in place, we can now protect routes in the application. We will use the getAuth function to check the authentication status of the user. If the user is not authenticated, we will redirect the user to the sign-in page. We will use this function in a new layout.tsx for the Dashboardâs page.tsx :
Now we cannot access the dashboard anymore without being authenticated. Eventually you want to protect more than the Dashboard page (e.g. Account page) and do not want to repeat the same logic for every page in its layout.tsx file. Therefore add a new Group Route folder:
And move the AuthenticatedLayout (or layout.tsx file) from before to the new shared layout.tsx file. Then remove the layout.tsx from the dashboard folder, because it is now shared across all pages in the (authenticated) group route. Now you could have the following project structure:
Itâs worth mentioning that we havenât used the authorization in Nextâs middleware. There is no 100% right or wrong way to do it. You can use Nextâs middleware for the authorization, but you can also use React Server Components. For example, a Next core developer tweeted against using Nextâs middleware for the authorization and also the creator of Lucia Auth says to use React Server Components for the authorization.
Last we want to show different navigation items based on the authentication status of the user. Again we will use the getAuth function from one of the previous steps:
Then the RootLayout needs to become an asynchronous function, which works because it is a React Server Component by default, and thus allows us to fetch the user before rendering the layout:
If the user is authenticated, we will show the dashboard link and the sign-out button. If the user is not authenticated, we will show the sign-up and sign-in links:
The interesting part is that even though we used getAuth at two places, it runs only once (and hits the cache for the second time), because we are using Reactâs cache function. This is a powerful feature of React Server Components with cached data fetching, because it allows us to fetch the same data at multiple places without worrying about the performance implications.
Thatâs it for the basic authentication flow in a Next.js application with Oslo. You can sign up, sign in, and sign out in the application. You can also protect routes and check the authentication status of the user. Next you may want to add more database models for the actual business domain and assign the userId as foreign key to it.
You can find the repository for this tutorial over here . If you want to go beyond this with the implementation of password change, password reset, forgot password and email verification, check out âThe Road to Nextâ and get on the waitlist!