How to add Auth0 to Nextjs- the Ultimate Guide

How to add Auth0 to Nextjs- the Ultimate Guide

A definitive guide for adding Auth0 to your Next.js project.

In this video we will show how you can add Auth0 to your Next.js application. This is something that used to be quite tricky but after the release of the api feature in Next.js which makes it super easy to add your own api-only endpoints to a Next.js application, the task of adding Auth0 became a lot more managable. This became dead simple after Auth0 created a auth-nextjs package which we will use in this video to set up all of our api endpoints.

Github Starter Project

First go ahead and clone our Next.js Auth0 Starter project on github. Make sure to checkout the start-here tag or you will end up with the finished project. I'm adding a -b development flag so that you will check out the tag and immediately create a development branch so that you won't be in a dreaded detached HEAD state. Feel free to name the branch whatever you'd like:

git checkout start-here -b development

Video of what we are doing:

Let’s first start by installing the @auth0/nextjs-auth0 package.

npm install @auth0/nextjs-auth0 --save

Next, we want to initialize auth0 on the server so that we can use it in all of our api methods. Make sure to define all of the variables in a .env file in the root of your project. You should be able to find the clientId, clientSecret, and domain from auth0 once you create a new single page application project. Make sure to add the redirect Uri and the postLogoutRedirectUri urls into the respective fields within your auth0 app so that auth0 knows which urls that it should trust during the entire redirect process.

utils/auth.ts

import { initAuth0 } from '@auth0/nextjs-auth0'

export default initAuth0({
  domain: process.env.domain,
  clientId: process.env.clientId,
  clientSecret: process.env.clientSecret,
  scope: 'openid profile',
  redirectUri: process.env.redirectUri,
  postLogoutRedirectUri: process.env.postLogoutRedirectUri,
  session: {
    cookieSecret: process.env.cookieSecret,
    cookieLifetime: 60 * 60 * 8,
    storeIdToken: false,
    storeAccessToken: false,
    storeRefreshToken: false,
  },
  oidcClient: {
    httpTimeout: 2500,
    clockTolerance: 10000,
  },
})
domain=your-auth0-domain.auth0.com
clientId=your-client-id
clientSecret=your-client-secret
redirectUri=http://localhost:3000/api/callback
postLogoutRedirectUri=http://localhost:3000/
cookieSecret=here-is-a-really-long-string-please-use-a-unique-one

In order for the .env variables to be recognized by Next.js we have one more step. Create a next.config.js file and add the following to it:

next.config.js

require('dotenv').config()

module.exports = {}

Make sure to restart your next.js development server after you do this or these settings won't get loaded in. Then, add dotenv to the project:

npm install --save dotenv

This will load the environmental variables from the .env into our app but they will only be accessible from the server, which is exactly what we want. Now let's go ahead and make our api routes. They are all very similar except we will be calling different methods from the auth0 instance that we are defining above.

pages/api/login.ts

import auth0 from '../../utils/auth0'

export default async function login(req, res) {
  try {
    await auth0.handleLogin(req, res, {})
  } catch (error) {
    console.error(error)
    res.status(error.status || 500).end(error.message)
  }
}

pages/api/callback.ts

import auth0 from '../../utils/auth0'

export default async function callback(req, res) {
  try {
    await auth0.handleCallback(req, res, {})
  } catch (error) {
    console.error(error)
    res.status(error.status || 500).end(error.message)
  }
}

pages/api/me.ts

import auth0 from '../../utils/auth0'

export default async function me(req, res) {
  try {
    await auth0.handleProfile(req, res, {})
  } catch (error) {
    console.error(error)
    res.status(error.status || 500).end(error.message)
  }
}

pages/api/profile.ss

import auth0 from '../../utils/auth0'

export default async function logout(req, res) {
  try {
    await auth0.handleLogout(req, res)
  } catch (error) {
    console.error(error)
    res.status(error.status || 500).end(error.message)
  }
}

We start by defining an async function that has a req and a res. This is similar to express middlewheress where we get access to the request through the req and modify the response by writing over pieces of the res object. In our case, we have a try/catch block where we try to call the auth0 method and pass the req and res into it. In the event that an error occurs, we log the error and return the error status and the error message.

Next, we need to create a way to store the user state on the client. The following code is pulled directly from the auth-nextjs example folder. The general idea is that we create a react context that will store the user profile information. We will create a provider component called UserProvider which we will have in the root of our project and we will call the useFetchUser react hook in any of the nested react components that need accesss to the user profile information.

utils/user.tsx

import React from 'react'
import fetch from 'isomorphic-unfetch'

// Use a global to save the user, so we don't have to fetch it again after page navigations
let userState

const User = React.createContext({ user: null, loading: false })

export const fetchUser = async () => {
  if (userState !== undefined) {
    return userState
  }

  const res = await fetch('/api/me')
  userState = res.ok ? await res.json() : null
  return userState
}

export const UserProvider = ({ value, children }) => {
  const { user } = value

  // If the user was fetched in SSR add it to userState so we don't fetch it again
  React.useEffect(() => {
    if (!userState && user) {
      userState = user
    }
  }, [])

  return <User.Provider value={value}>{children}</User.Provider>
}

export const useUser = () => React.useContext(User)

export const useFetchUser = () => {
  const [data, setUser] = React.useState({
    user: userState || null,
    loading: userState === undefined,
  })

  React.useEffect(() => {
    if (userState !== undefined) {
      return
    }

    let isMounted = true

    fetchUser().then(user => {
      // Only set the user if the component is still mounted
      if (isMounted) {
        setUser({ user, loading: false })
      }
    })

    return () => {
      isMounted = false
    }
  }, [userState])

  return data
}

Let's now update the MainLayout component to add the UserProvider. Since the MainLayout was a class based component we need to also convert it to a functional component because hooks require functional components to function. We will also call the useFetchUser hook here so that we can feed the user and loading boolean into the provider itself.

components/layout/MainLayout.tsx

import { Layout } from 'antd'
import { ReactNode, Component } from 'react'
import Navbar from './Navbar'
import styled from 'styled-components'
import { UserProvider, useFetchUser } from '../../utils/user'

const { Content } = Layout

const StyledContent = styled(Content)`
  min-height: 100vh;
`

export const MainLayout = ({ children }: { children: ReactNode }) => {
  const { user, loading } = useFetchUser()
  return (
    <UserProvider value={{ user, loading }}>
      <Layout>
        <Navbar />
        <StyledContent>{children}</StyledContent>
      </Layout>
    </UserProvider>
  )
}

We are now ready to update the Navbar component to add the useFetchUser hook and use the user object that we get back as a way to tell if the user is logged in or not. If its undefined we can assume the user is not logged in an display the login button. Otherwise if there is a user object then we know they are logged in and we can display the logout and profile buttons:

components/layout/Navbar.tsx

import { Layout, Menu } from 'antd'
import Link from 'next/link'
import styled from 'styled-components'
import { useFetchUser } from '../../utils/user'

const { Header } = Layout

const StyledHeader = styled(Header)`
  background-color: #dddbe8;
  .ant-menu {
    width: 100%;
    background-color: #dddbe8;
    a {
      height: 64px;
    }
  }
`

const Navbar = () => {
  const { user, loading } = useFetchUser()

  return (
    <StyledHeader>
      <Menu mode="horizontal">
        <Menu.Item key="/">
          <Link href="/">
            <a>Home</a>
          </Link>
        </Menu.Item>
        {user && !loading
          ? [
              <Menu.Item key="/api/logout">
                <Link href="/api/logout">
                  <a>Logout</a>
                </Link>
              </Menu.Item>,
              <Menu.Item key="/profile">
                <Link href="/profile">
                  <a>Profile</a>
                </Link>
              </Menu.Item>,
            ]
          : null}
        {!user && !loading ? (
          <Menu.Item key="/api/login">
            <Link href="/api/login">
              <a>Login</a>
            </Link>
          </Menu.Item>
        ) : null}
      </Menu>
    </StyledHeader>
  )
}

export default Navbar

Finally, let's update the profile page so that we can display the user's profile information if they are logged in. Otherwise, we will redirect them to the home page.

pages/profile.tsx

import { MainLayout } from '../components/layout/MainLayout'
import styled from 'styled-components'
import { useFetchUser } from '../utils/user'
import Router from 'next/router'

const StyledProfile = styled.div`
  padding: 50px 10px;
  text-align: center;
  h1 {
    font-size: 60px;
  }
`

export default function Profile() {
  const { user, loading } = useFetchUser()

  if (loading) {
    return (
      <MainLayout>
        <p>Loading...</p>
      </MainLayout>
    )
  }
  if (!user && !loading) {
    Router.replace('/')
  }

  return (
    <MainLayout>
      <StyledProfile>
        <h1>🤸</h1>
        <p>Welcome to the Profile Page! Here is your profile information:</p>
        <p>{JSON.stringify(user)}</p>
      </StyledProfile>
    </MainLayout>
  )
}

That's it! You should now have a working Next.js application with Auth0 and are now ready to build the next great web application with this as a starter. Cheers!

If you liked this tutorial, I created an entire course that teaches you how to build a recipe sharing application from the ground up using Next.js, Auth0 and a graphQL CMS called GraphCMS. We will go through how to take the foundation that you learned here and build a fully featured application that has file uploads, user permisssions, responsive design with Ant Design, and the entire app is hosted serverlessly using Zeit's Now service so that you can deploy it with one command and be confident that it will be reliable and scalable no matter how many people visit the page.

Frontend Serverless with React and GraphQL

Frontend Serverless with React and GraphQL

Learn something new? Share it with the world!

There is more where that came from!

Drop your email in the box below and we'll let you know when we publish new stuff. We respect your email privacy, we will never spam you and you can unsubscribe anytime.