When we develop applications we are focused on the user, and when considering the user, two important factors are identification and access. Identification, also referred to as authentication, checks that a user is who they claim to be, whereas access, also referred to as authorization, confirms the user’s permissions.
As a full-stack framework Next.js is significantly more adaptable when it comes to authentication than React, where the main focus is simply on authenticating client-side code. However, when building with Next.js, you must consider authentication from several perspectives:
Client-side authentication
Server-side authentication
API routes authentication
Other components might include user data, such as whether we need to persist user data or not; if we don’t, we can utilize authentication services such as Github, Facebook, and others; if we do, we’ll need a database.
If we think about managing all these, writing your own code from scratch would take an eternity, so to make things easier we are going to use a library called NextAuth.js. It has built-in support for many services like Apple, Xero, Discord, Facebook, GitHub, Twitter, and plenty more. If you’re not interested in any of those you can have password-free authentication with email and custom username password credential authentication with support for many popular databases like MySQL, Postgres SQL server, MongoDB, etc. It exposes several methods and hooks that will make developer life easier.
In this article we will learn how to set up the next auth library using GitHub as an auth provider. To get us started I’ve created a brand new next.js project called next auth using create next app and I’ve also added some code which you can find at this link.
Let me walk you through everything I’ve put in the project folder. We have a components folder with two files, navbar.js and navbar.css. As the name suggests, these components are for the navbar in our application with links to the home dashboard blog sign in and sign out.
The CSS file includes the styling for the navbar. Because I didn’t use CSS modules, I had to import the file in _app.js where we have the navbar as part of the page layout. In addition to this navbar component, I’ve included two pages, dashboard.js and blog.js, both of which return an h1 tag.
npm install --save next-auth
The simplest approach to getting started is to refer to our documents’ getting started section. For individuals searching for more detailed examples, we also provide a lesson area.
For further information and documentation, go to next-auth.js.org.
We can configure next auth with a GitHub auth provider. There is a convention to this, so for step two in the API folder create a new folder called auth. Within this folder we need to create a catch-all route […nextauth].js; within the file we are going to import two things, the next auth library itself (import next auth from ‘next-auth’) and providers from ‘next-auth/providers’. Now we’re going to default export next auth, passing in a configuration object, so export default next auth. We pass in an object that accepts several keys, out of which providers is one of them. It refers to the services that can be used to sign in a user. Since you can configure multiple services, this property is an array for our example, though we will take a look only at GitHub within the array providers.GitHub. Add parentheses and this accepts an object with the client ID and the client secret. These are values generated on a GitHub app that follow the following docs.
For this article, we can make two fake strings in the .env.local file to be referenced as id and secret
[…nextauth].js
1 2 3 4 5 6 7 8 9 10 11
import NextAuth from 'next-auth' import Providers from 'next-auth/providers' export default NextAuth({ providers: [ Providers.GitHub({ clientId: process.env.GITHUB_ID, clientSecret: process.env.GITHUB_SECRET }) ], })
Now hit API http://localhost:3000/api/auth/signin and push the sign-in button then authorize the connection request.
When you approve you’ll be redirected to the home page. The way we verify if we have signed in is by taking a look at cookies.
In the cookies section, there is a next auth session token, which is a JWT used for authentication by the next odd package. To sign out, visit localhost:3000/api/auth/signout. Once again, we have a built-in UI for the sign-out route; click sign out, and the session token is deleted from the cookies, and we are routed to the home page.
Let’s configure the next auth package using GitHub as an auth provider. We learned that the next auth package handles calls to /api/auth/signin and /api/auth/signout with the aid of this catch-all route. These functions are accommodated on our navbar.
Navbar.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
import Link from 'next/link'
import {
signIn,
signOut
} from 'next-auth/client'
function Navbar() {
return (
<nav className='header'>
<h1 className='logo'>
<a href='#'>NextAuth</a>
</h1>
<ul className='main-nav'>
<li>
<Link href='/'>
<a>Home</a>
</Link>
</li>
<li>
<Link href='/dashboard'>
<a>Dashboard</a>
</Link>
</li>
<li>
<Link href='/blog'>
<a>Blog</a>
</Link>
</li>
<li>
<Link href='/api/auth/signin'>
<a
onClick={e => {
e.preventDefault()
signIn('github')
}}>
Sign In
</a>
</Link>
</li>
<li>
<Link href='/api/auth/signout'>
<a
onClick={e => {
e.preventDefault()
signOut()
}}>
Sign Out
</a>
</Link>
</li>
</ul>
</nav>
)
}
export default Navbar
In client-side authentication, we are looking at showing or hiding content in the UI based on whether a user is logged in or not. For our scenario, we’re going to work with the navbar. Irrespective of whether the user is signed in or signed out, the sign in and sign out buttons are always present. This is something we don’t want in a production app, the sign-in button should only be visible if the user is signed out and the sign out button should only be visible if the user is signed. The way we check if someone is signed in with the next auth library is by using the use session hook that the package provides. Along with sign in and sign out, import useSession, then within the component call the hook. The hook returns two values: the first value is the session object and the second value is the loading flag.
Navbar.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
import Link from 'next/link'
import {
signIn,
signOut,
useSession
} from 'next-auth/client'
function Navbar() {
const [session, loading] = useSession()
return (
<nav className='header'>
<h1 className='logo'>
<a href='#'>NextAuth</a>
</h1>
<ul className={`main-nav {!session && loading ? 'loading' : 'loaded'}`}>
<li>
<Link href='/'>
<a>Home</a>
</Link>
</li>
<li>
<Link href='/dashboard'>
<a>Dashboard</a>
</Link>
</li>
<li>
<Link href='/blog'>
<a>Blog</a>
</Link>
</li>
{!loading && !session && (
<li>
<Link href='/api/auth/signin'>
<a
onClick={e => {
e.preventDefault()
signIn('github')
}}>
Sign In
</a>
</Link>
</li>
)}
{session && (
<li>
<Link href='/api/auth/signout'>
<a
onClick={e => {
e.preventDefault()
signOut()
}}>
Sign Out
</a>
</Link>
</li>
)}
</ul>
</nav>
)
}
export default Navbar
Let’s learn how to secure pages client-side. For our example we’re going to make use of the dashboard page. Even if a user is not logged in they’re allowed to view the dashboard page, let’s fix that. UseSession hook gives us access to the session in the react component. That is a good starting point, so open dashboard.js and get hold of the session. At the top we import use session from next-auth/client and within the component we get back session and loading from the useSession hug.
Instead of useSession, import get session from the next-auth/client. The function returns a promise with a session object or null if no session exists. Unlike the use session hook, though, we need to maintain our own loading state coupled with the use effect hook to call the getSession function on page load.
dashboard.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import {
useState,
useEffect
} from 'react'
import {
getSession,
signIn
} from 'next-auth/client'
function Dashboard() {
const [loading, setLoading] = useState(true)
useEffect(() => {
const securePage = async () => {
const session = await getSession()
console.log({
session
})
if (!session) {
signIn()
} else {
setLoading(false)
}
}
securePage()
}, [])
if (loading) {
return <h2>Loading...</h2>
}
return <h1>Dashboard page</h1>
}
export default Dashboard
NextAuth provider secures pages with the get session function. We saw that the use session hug is always in a loading state, which is why we have to resort to this get session function. We can change that behavior by adding a provider that the next auth library offers. Let’s instead work with the home page in index.js. We are going to import the use session hook and call it within the component, so import new session from next-auth/client and within the component const session, loading is equal to useSession.
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
import Head from 'next/head'
import Image from 'next/image'
import styles from '../styles/Home.module.css'
import {
useSession
} from 'next-auth/client'
export default function Home() {
const [session, loading] = useSession()
console.log({
session,
loading
})
return (
<div className={styles.container}>
<Head>
<title>Create Next App</title>
<meta name='description' content='Generated by create next app' />
<link rel='icon' href='/favicon.ico' />
</Head>
<main className={styles.main}>
<h1 className={styles.title}>
{session ? `{session.user.name}, ` : ''}Welcome to{' '}
<a href='https://nextjs.org'>Next.js!</a>
</h1>
<p className={styles.description}>
Get started by editing{' '}
<code className={styles.code}>pages/index.js</code>
</p>
<div className={styles.grid}>
<a href='https://nextjs.org/docs' className={styles.card}>
<h2>Documentation →</h2>
<p>Find in-depth information about Next.js features and API.</p>
</a>
<a href='https://nextjs.org/learn' className={styles.card}>
<h2>Learn →</h2>
<p>Learn about Next.js in an interactive course with quizzes!</p>
</a>
<a
href='https://github.com/vercel/next.js/tree/master/examples'
className={styles.card}>
<h2>Examples →</h2>
<p>Discover and deploy boilerplate example Next.js projects.</p>
</a>
<a
href='https://vercel.com/new?utm_source=create-next-app&utm_medium=default-template&utm_campaign=create-next-app'
className={styles.card}>
<h2>Deploy →</h2>
<p>
Instantly deploy your Next.js site to a public URL with Vercel.
</p>
</a>
</div>
</main>
<footer className={styles.footer}>
<a
href='https://vercel.com?utm_source=create-next-app&utm_medium=default-template&utm_campaign=create-next-app'
target='_blank'
rel='noopener noreferrer'>
Powered by{' '}
<span className={styles.logo}>
<Image src='/vercel.svg' alt='Vercel Logo' width={72} height={16} />
</span>
</a>
</footer>
</div>
)
}
The next auth library gives us a provider which we can use at the top of our component tree. In an xjs application that would typically be _app.js,
so at the very top, import provider from next-auth/client and then instead of the fragment using the provider:
_app.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import Navbar from '../components/Navbar'
import '../styles/globals.css'
import '../components/Navbar.css'
function MyApp({
Component,
pageProps
}) {
return (
<Provider>
<Navbar />
<Component {...pageProps} />
</Provider>
)
}
Export default MyApp.
Server-side authentication needs to be more precise authentication inside the get server-side props function, which caters to server-side rendering about client-side authentication, where we get hold of the current session and determine what UI to render. The concept remains the same for server-side rendering as well. We first get hold of the current session but after that, instead of determining what UI to render, we determine what props to send from get server side props to the component in blog.js. We’re going to add the getServerSideProps function now. If you’re wondering why not getStaticProps , remember that getStaticProps runs at build time, while getServerSideProps runs for every incoming request and will contain information about that incoming request.
blog.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import {
getSession
} from 'next-auth/client'
function Blog({
data
}) {
return <h1>Blog page - {data}</h1>
}
export default Blog
export async function getServerSideProps(context) {
const session = await getSession(context)
return {
props: {
data: session ? 'List of paid blogs' : 'List of free blogs',
}
}
}
Do you remember this transition which we added to the nav links? We added that because of the flicker in the sign in and sign out text on the UL. To remove the classes apart from main and correct we can pass in the session object as a prop from getting server-side props and then on the provider we can specify a session prop and assign pageProps.session. PageProps here refers to the prop returned from getting server site props.
<Provider session={pageProps.session}
Inside navbar.js useSession first checks with the provided context, if the session is present. If it is, it uses that right away without having to figure out if the session is loaded. If we refresh the browser we won’t see the flicker in the nav link when the page loads session is figured out server-side and then passed into the navbar through the provider and use session-no loading session meaning no flicker effect.
At the moment, if the user is not logged into the blog page, we are returning a list of free blog posts. Let’s say we become greedy and we want the user to log in to access this page. So, if the user is logged in they can view the page but if they’re not logged in they should be redirected to the sign-in page to do that. In the getServerSideProps function we check if the session is null, in other words the user has not logged in, so if not session we’re going to return from the function.
If we don’t return props we have to return a redirect object. This object will contain two key destinations which are the sign-in route, so /api/auth/signin, and we’re also going to append the redirect URL so callback URL is equal to http://localhost:3000/blog.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import {
getSession,
useSession
} from 'next-auth/client'
function Blog({
data
}) {
const [session] = useSession()
console.log({
session
})
return <h1>Blog page - {data}</h1>
}
export default Blog
export async function getServerSideProps(context) {
const session = await getSession(context)
if (!session) {
return {
redirect: {
destination: '/api/auth/signin?callbackUrl=http://localhost:3000/blog',
permanent: false
}
}
}
return {
props: {
data: 'List of paid blogs',
session
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import {
getSession
} from 'next-auth/client'
export default async (req, res) => {
const session = await getSession({
req
})
if (!session) {
res.status(401)
.json({
error: 'Unauthenticated user'
})
} else {
res.status(200)
.json({
message: 'Success',
session
})
}
}
Several authentication services can assist us in adding an additional degree of protection to our protected routes:
Firebase is a fantastic platform for user management and authentication.
Auth0 Next.js SDK is a library for using Next.js to enable user authentication.
Passport is a Node.js authentication middleware. It is extremely versatile and modular, and can be seamlessly integrated into any Express-based online application.
Supabase is an open-source Firebase replacement. It offers all of the backend services required to construct a product.