Authentication in Nextjs
Tags: nextjs, react, webdev, tutorial
Published: 2023-04-13 16:23:43

---
title: Authentication in Nextjs
published: true
series: Authentication Series
date: 2023-04-13 15:05:56 UTC
tags: nextjs,react,webdev,tutorial
canonical_url: https://blog.rrrm.co.uk/post/authentication-in-nextjs
---

Most developers are familiar with the popular NextAuth.js plugin for handling authentication in Next.js applications. It's a powerful and easy-to-use tool that simplifies the process of adding authentication to your project. However, some developers prefer to avoid using third-party plugins and instead implement authentication themselves using custom code.

In this context, it's worth mentioning that Next.js provides various tools and features for handling authentication without relying on external plugins. One of these tools is middleware, which is a function that runs before your page component is rendered and can be used to implement authentication logic.

Recently, I wrote a blog about implementing[ authentication in Nuxt 3](https://blog.rrrm.co.uk/post/authentication-in-nuxt-3) using middleware, and I wanted to see if it was possible to achieve the same results in Next.js. In this blog, I will be converting my Nuxt 3 project into a Next.js project and exploring how to use middleware to handle authentication. By the end of this blog, you will have a clear understanding of how to implement authentication in Next.js without relying on third-party plugins.

lets start by creating a blank nextjs project 

`npx create-next-app@latest`

select your options I just used typescript as default 

install  `zustand`

`npm i zustand`

I choose `zustand` as my state management library because it looks easy and straight forward.

exactly like the tutorial in my Nuxt 3 post, I will be using  [DummyJSON](https://dummyjson.com/) 

###  store
 
 path: `store/useAuthStore.ts`
 
```javascript

// Importing create function from the Zustand library
import { create } from 'zustand'

// Defining an interface for the store's state
interface AuthStoreInterface {
  authenticated: boolean // a boolean value indicating whether the user is authenticated or not
  setAuthentication: (val: boolean) => void // a function to set the authentication status
  user: any // an object that stores user information
  setUser: (user: any) => void // a function to set user information
}

// create our store
export const useAuthStore = create<AuthStoreInterface>((set) => ({
  authenticated: false, // initial value of authenticated property
  user: {}, // initial value of user property
  setAuthentication: (val) => set((state) => ({ authenticated: val })), // function to set the authentication status
  setUser: (user) => set({ user }), // function to set user information
}))

```

Overall, this code creates a store for authentication-related state in a React application using Zustand. It provides methods to set and update the authentication and user information stored in the state.

### Layout

I also wanted to play around with the new layouts feature in next so I created a layout folder

path: `layouts/DefaultLayouts/index.tsx`

```javascript
// Importing necessary components and functions
import Navbar from '~/components/Navbar' // a component for the website navigation bar
import Footer from '~/components/Footer' // a component for the website footer
import { useEffect } from 'react' // importing useEffect hook from react
import { getCookie } from 'cookies-next' // a function to get the value of a cookie
import { useAuthStore } from '~/store/useAuthStore' // a hook to access the authentication store

// Defining the layout component
export default function Layout({ children }: any) {
  // Getting the token value from a cookie
  const token = getCookie('token')

  // Getting the setAuthentication function from the authentication store
  const setAuthentication = useAuthStore((state) => state.setAuthentication)

  // Running a side effect whenever the token value changes
  useEffect(() => {
    console.log(token) // Logging the token value for debugging purposes
    if (token) {
      setAuthentication(true) // Setting the authentication status to true if a token exists
    }
  }, [token])

  // Rendering the layout with the Navbar, main content, and Footer components
  return (
    <>
      <Navbar />
      <main className="mainContent">{children}</main>
      <Footer />
    </>
  )
}
```
Pretty straight forward we have our Navbar and Footer component and we wrap the main container between both, we are also calling the store to check if token is there and setting the authentication state 

Now just need to use our layout in the `_app.tsx` file

```jsx
import '../styles/globals.scss'
import Layout from '~/layouts/DefaultLayout'
import { AppProps } from 'next/app'

export default function App({ Component, pageProps }: AppProps) {
  return (
    <Layout>
      <Component {...pageProps} />
    </Layout>
  )
}

```

### middleware 

the code below defines a middleware function to handle user authentication in Next.js. The function checks whether a user has a token (stored in a cookie) to access protected routes. If the user does not have a token and the requested path is not allowed, the middleware will redirect them to the signin page. If the user is already authenticated and tries to access a path that is allowed, the middleware will redirect them to the home page. This function also ignores any routes that start with /api and /_next to avoid running the middleware multiple times.

```javascript

import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'

export function middleware(request: NextRequest) {
  const { pathname } = request.nextUrl
  /* ignore routes starting with api and _next (temp solution)
    matchers in next.config isn't working
    without this the middleware will run more than once
   so to avoid this we will ignore all paths with /api and  /_next
   */
  if (
    request.nextUrl.pathname.startsWith('/api/') ||
    request.nextUrl.pathname.startsWith('/_next/')
  ) {
    return NextResponse.next()
  }

  // our logic starts from here

  let token = request.cookies.get('token')?.value // retrieve the token
  const allowedRoutes = ['/auth/signin', '/auth/register'] // list of allowed paths user can visit without the token
  const isRouteAllowed = allowedRoutes.some((prefix) => pathname.startsWith(prefix)) // check path and see if matches our list then return a boolean

  // redirect to login if no token
  if (!token) {
    if (isRouteAllowed) {
      // check if path is allowed
      return NextResponse.next()
    }
    // if path is not allowed redirect to signin page
    return NextResponse.redirect(new URL('/auth/signin', request.url))
  }

  //redirect to home page if logged in
  if (isRouteAllowed && token) {
    return NextResponse.redirect(new URL('/', request.url))
  }
}

```

### Pages

The `index` page

```javascript
import Head from 'next/head'

export default function Home() {
  return (
    <div>
      <h1>Homepage</h1>
    </div>
  )
}
```

The `SignIn` page

```javascript

import { NextPage } from 'next'
import { useState } from 'react'
import { setCookie } from 'cookies-next'
import { useRouter } from 'next/router'
import { useAuthStore } from '~/store/useAuthStore' // import our useAuthStore

const SignIn: NextPage = (props) => {
  // set UserInfo state with inital values
  const [userInfo] = useState({ email: 'kminchelle', password: '0lelplR' })
  const router = useRouter()

  // import state from AuthStore
  const setUser = useAuthStore((state) => state.setUser)
  const setAuthentication = useAuthStore((state) => state.setAuthentication)

  const login = async () => {
    // do a post call to the auth endpoint
    const res = await fetch('https://dummyjson.com/auth/login', {
      method: 'post',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        username: userInfo.email,
        password: userInfo.password,
      }),
    })

    // check if response was ok
    if (!res.ok) {
      return console.error(res)
    }
    // retrieve data from the response
    const data = await res.json()

    // check if we have data
    if (data) {
      setUser(data) // set data to our user state
      setAuthentication(true) // set our authentication state to true
      setCookie('token', data?.token) // set token to the cookie
      router.push('/') // redirect to home page
    }
  }

  return (
    <div>
      <div className="title">
        <h2>Login</h2>
      </div>
      <div className="container form">
        <label>
          <b>Username</b>
        </label>
        <input
          type="text"
          className="input"
          placeholder="Enter Username"
          name="uname"
          value={userInfo.email}
          onChange={(event) => (userInfo.email = event.target.value)}
          required
        />
        <label>
          <b>Password</b>
        </label>
        <input
          type="password"
          className="input"
          placeholder="Enter Password"
          value={userInfo.password}
          onChange={(event) => (userInfo.password = event.target.value)}
          name="psw"
          required
        />
        <button onClick={login} className="button">
          Login
        </button>
      </div>
    </div>
  )
}

export default SignIn

```

The code provided is sufficient to implement authentication in Next.js without using the NextAuth plugin. However, it should be noted that this code only supports email and password authentication, and not Single Sign-On (SSO) options like Google or GitHub. During a project that involved converting a Nuxt 3 project to a Next.js project, I found Zustand to be a useful tool. In comparison to Redux and Context, Zustand is lightweight and more preferable.

The middleware function in Next.js is a valuable addition. However, I did encounter some issues with the matchers while using it, but was able to find workarounds to solve the problems.

Full project layout

```
components/
   - Footer 
   - Navbar
layouts/
  - DefaultLayout
pages/
 - auth/         
    - login.tsx    <-- login page
    - register.tsx     <-- /register page
  - index.tsx  <- homepage
  - _app.tsx <-  nextjs app file
store/
  - useAuthStore.ts  <- zustand store
styles/
  - globals.scss  <- global styleguide
middleware.ts  <- middleware file in root of project    
```

Preview: [https://next-auth-example-mu-two.vercel.app/](https://next-auth-example-mu-two.vercel.app/)
Repo:  [https://github.com/rafaelmagalhaes/next-auth-example](https://github.com/rafaelmagalhaes/next-auth-example)