The Complete Guide to Next.js Authentication
Tags: aws, nextjs, react, javascript
Published: 2020-09-21 13:14:42

---
title: The Complete Guide to Next.js Authentication
published: true
description: The complete guide to Next.js authentication  with AWS - Client, Server, and API routes
tags: aws, nextjs, react, javascript
cover_image: https://dev-to-uploads.s3.amazonaws.com/i/qo6msqyoi9rbji7pacov.jpg
series: Building Scalable Next.js Apps on AWS
---
> Cover image by [Kai Pilger](https://unsplash.com/@kaip)

In this guide you will learn how to implement authentication in a Next.js app. I will cover client authentication, authenticated server-rendered pages, authenticated API routes, protected routes, and redirects.

The authentication service will be implemented with [AWS Amplify](https://docs.amplify.aws/), but the ideas and strategies covered here will work for any authentication service like [Auth0](https://auth0.com/) / [Okta](https://www.okta.com/) or even a custom back end implementation as long as it provides a way to manage sessions across the client and server.

> The code for this project is located [here](https://github.com/dabit3/next.js-authentication-aws). Video walkthrough is [here](https://dev.to/dabit3/the-complete-guide-to-next-js-authentication-2aco#video-walkthrough).

---

## Next.js Overview

Next.js combines client-side rendering with pre-rendered HTML in the form of static and server-rendered pages. The framework also makes it really easy to create APIs with API routes.

When running a build, the framework will determine whether a page should be generated statically or if it should be a server-rendered. By default all pages are statically generated unless the page is using the `getServerSideProps` function to pass props into the page. Also, all API routes will by default be server rendered.

## Next.js Authentication Concepts

When working within a Next.js app you typically want to take advantage of all of these features and have your APIs work seamlessly across the framework (client and server). The problem that it is often not easy to securely access the user session on both the client and the server.

In this guide, I'll show you how to enable user authentication and authorization to implement the following:

1. Client authentication
2. Accessing the user session on the client
3. Protected client routes
4. Client-side redirects
5. Accessing the user session in a server-side route (`getServerSideProps`)
6. Protected server routes (`getServerSideProps`)
7. Server-side redirects (`getServerSideProps`)
8. Accessing the user session in an API route
9. Social sign-in (OAuth)
10. Deploying the app using the Next.js Serverless Component

## Getting started

To get started, first create a new Next.js app:

```sh
npx create-next-app next-authentication
```

Next, change into the new directory and install the dependencies:

```sh
cd next-authentication
npm install aws-amplify @aws-amplify/ui-react emotion
```

Next, initialize a new Amplify project:

```sh
amplify init

> Choose defaults when prompted
```

> If you do not yet have the Amplify CLI installed and configured, see [this video](https://www.youtube.com/watch?v=n4DuYTzpvdE) for a full walkthrough.

Next, add the authentication service:

```sh
amplify add auth

? Do you want to use the default authentication and security configuration? Default configuration
? How do you want users to be able to sign in? Username
? Do you want to configure advanced settings? No, I am done.
```

Next, deploy the authentication service:

```sh
amplify push --y
```

### Enabling Amplify SSR

Next, to enable Amplify SSR support, open  __pages/_app.js__ and add the following at the top of the file:

```js
import Amplify from 'aws-amplify'
import config from '../src/aws-exports'
Amplify.configure({
  ...config,
  ssr: true
})
```

🔥 Setting `ssr` to `true` is all you need to do to make your Amplify app SSR aware.

## Creating the auth / profile route

Next, create a new file in the __pages__ directory called __profile.js__.

Here, we will enable authentication by using the `withAuthenticator` component. This component will create a user authentication flow, enabling a user to sign up with MFA and sign in.

In this file, add the following code:

```js
// pages/profile.js
import { useState, useEffect } from 'react'
import { Auth } from 'aws-amplify'
import { withAuthenticator, AmplifySignOut } from '@aws-amplify/ui-react'

function Profile() {
  const [user, setUser] = useState(null)
  useEffect(() => {
    // Access the user session on the client
    Auth.currentAuthenticatedUser()
      .then(user => {
        console.log("User: ", user)
        setUser(user)
      })
      .catch(err => setUser(null))
  }, [])
  return (
    <div>
      { user && <h1>Welcome, {user.username}</h1> }
      <AmplifySignOut />
    </div>
  )
}

export default withAuthenticator(Profile)
```

Finally, update __pages/_app.js__ to add some navigation to link between pages:

```js
import '../styles/globals.css'
import Link from 'next/link'
import { css } from 'emotion'

import Amplify from 'aws-amplify'
import config from '../src/aws-exports'
Amplify.configure({
  ...config,
  ssr: true
})

export default function MyApp({ Component, pageProps }) {
  return (
    <div>
      <nav className={navStyle}>
        <Link href="/">
          <span className={linkStyle}>Home</span>
        </Link>
        <Link href="/profile">
          <span className={linkStyle}>Profile</span>
        </Link>
      </nav>
      <Component {...pageProps} />
    </div>
  )
}

const linkStyle = css`
  margin-right: 20px;
  cursor: pointer;
`

const navStyle = css`
  display: flex;
`
```

### Optional - Styling the component

You can configure styling for the authentication component. For example, to try and match the blue color scheme that the Next.js starter ships with, you can add the following to the bottom of __styles/globals.css__:

```css
:root {
  --amplify-primary-color: #0083e8;
  --amplify-primary-tint: #006ec2;
  --amplify-primary-shade: #006ec2;
}
```

### Creating an account and signing in

Now that the Profile route has been created, let's test it out by creating a new account and signing in.

```sh
npm run dev
```

> Click [here](https://docs.amplify.aws/ui/auth/authenticator/q/framework/react) for more ways to customize the `withAuthenticator` component.

You should be able to navigate to the __/profile__ route to create an account and sign in.

### Using the Auth class directly

If you want to build your own custom authentication flow, you can also leverage the [Auth class](https://docs.amplify.aws/lib/auth/emailpassword/q/platform/js) which has over 30 methods for managing user authentication state, including methods like `signUp`, `confirmSignUp`, `signIn`, and `forgotPassword`.

## Accessing user session in an SSR route

Now that users can sign in, let's create a new route to test out SSR.

Create a new route called __/protected.js__ in the pages directory.

Here, we want to have a route that authenticates the user on the server and returns either a success or error message based on the user's authentication state.

```js
// pages/protected.js

import { withSSRContext } from 'aws-amplify'

function Protected({ authenticated, username }) {
  if (!authenticated) {
    return <h1>Not authenticated</h1>
  }
  return <h1>Hello {username} from SSR route!</h1>
}

export async function getServerSideProps(context) {
  const { Auth } = withSSRContext(context)
  try {
    const user = await Auth.currentAuthenticatedUser()
    console.log('user: ', user)
    return {
      props: {
        authenticated: true, username: user.username
      }
    }
  } catch (err) {
    return {
      props: {
        authenticated: false
      }
    }
  }
}

export default Protected
```

Then update the __nav__ in __pages/_app.js__ with a link to the new route:

```js
<Link href="/protected">
  <span className={linkStyle}>Protected route</span>
</Link>
```

Now, when you are signed in you will notice that you will be able to access the authenticated user in the `getServerSideProps` method. You should also see the user object logged out to the terminal.

This is done using the `withSSRContext` function to destructure `Auth` from `aws-amplify` and making a call to `Auth.currentAuthenticatedUser()`. When gaining access to the `Auth` class in this way, Amplify automatically will read the request object and give you access to the signed in user's session on both API routes as well as SSR routes.

## Accessing user session in an API route

In this API route, we want to access the user and return either null for a user who is not authenticated or the username for a user who is authenticated.

To do so, create a new file in __pages/api__ called __check-user.js__:

```js
// pages/api/check-user.js
import Amplify, { withSSRContext } from 'aws-amplify'
import config from "../../src/aws-exports.js"

// Amplify SSR configuration needs to be enabled within each API route
Amplify.configure({ ...config, ssr: true })

export default async (req, res) => {
  const { Auth } = withSSRContext({ req })
  try {
    const user = await Auth.currentAuthenticatedUser()
    res.json({ user: user.username })
  } catch (err) {
    res.statusCode = 200
    res.json({ user: null })
  }
}
```

When you navigate or try to access __/api/check-user__ you will notice that the user object is available when you are authenticated and not available when you are not authenticated.

## Client-side redirect

Often you will want to detect whether a user is signed in and either allow access or redirect them based on whether they are authenticated or based on their credentials.

To do this you can use the `withRouter` hook from Next.js to programmatically route based on user state. Let's try this out.

Create a new file in the __pages__ directory called __protected-client-route.js__.

Here, add the following code:

```js
import { useState, useEffect } from 'react'
import { Auth } from 'aws-amplify'
import { useRouter } from 'next/router'

function ProtectedClient() {
  const [user, setUser] = useState(null)
  const router = useRouter()
  useEffect(() => {
    Auth.currentAuthenticatedUser()
      .then(user => setUser(user))
      // if there is no authenticated user, redirect to profile page
      .catch(() => router.push('/profile'))
  }, [])
  if (!user) return null
  return <h1>Hello {user.username} from client route!</h1>
}

export default ProtectedClient
```

Next, add a link to this route in __pages/_app.js__:

```js
<Link href="/protected-client-route">
  <span className={linkStyle}>Protected client route</span>
</Link>
```

If you try to access the protected client route you will be automatically redirected to the __profile__ route if you are not authenticated, and allowed to view the page if you are authenticated.

## Server-side redirects

One of the benefits of SSR is the ability to implement server-side redirects. Using a server-side redirect is more secure in that you have the option to not render any html at all, instead redirecting the user to another page.

Open __pages/protected.js__ and update with the following code:

```js
// pages/protected.js
import { withSSRContext } from 'aws-amplify'

function Protected({ username }) {
  return <h1>Hello {username} from SSR route!</h1>
}

export async function getServerSideProps({ req, res }) {
  const { Auth } = withSSRContext({ req })
  try {
    const user = await Auth.currentAuthenticatedUser()
    return {
      props: {
        authenticated: true,
        username: user.username
      }
    }
  } catch (err) {
    res.writeHead(302, { Location: '/profile' })
    res.end()
  }
  return {props: {}}
}

export default Protected
```

When you attempt to access this route, you will be redirected to the __profile__ route if you are not signed in.

## Social sign-in (OAuth)

To add social sign in, run `amplify update auth` and choose __Apply default configuration with Social Provider__.

From here you can add social sign in with Google, Facebook, or Amazon.

Once social sign in has been enabled, you can then sign users in from your app using the following code:

```js
// username / password + all OAuth providers
Auth.federatedSignIn()

// specifying an OAuth provider
<button onClick={() => Auth.federatedSignIn({provider: 'Facebook'})}>Open Facebook</button>
<button onClick={() => Auth.federatedSignIn({provider: 'Google'})}>Open Google</button>
<button onClick={() => Auth.federatedSignIn({provider: 'Amazon'})}>Open Amazon</button>
```

## Deploying the Next.js app to AWS with the Serverless Framework

To deploy the app to AWS using the Serverless Framework and the [Serverless Next Component](https://github.com/serverless-nextjs/serverless-next.js), first create a file called `serverless.yml` at the root of your application.

Next, add the following two lines of configuration (feel free to change __myNextApp__ to whatever name you'd like to use):

```yml
myNextApp:
    component: "@sls-next/serverless-component@1.17.0" 
```

Next, deploy using `npx`:

```sh
npx serverless
```

> If you've never used an AWS CLI, you may have to configure your AWS credentials. See basic instructions [here](https://github.com/serverless-nextjs/serverless-next.js#getting-started)

## Video Walkthrough

{% youtube S25rmxiOBz4 %}

## Conclusion

The final code for this project is located [here](https://github.com/dabit3/next.js-authentication-aws)

Big shout out to [Eric Clemmons](https://twitter.com/ericclemmons) of the Amplify team who spearheaded this project and built this functionality into Amplify.

For part 2, we will be learning how to combine Auth and Data to accomplish the following:

1. Fetching data in `getStaticPaths` for hydrating during SSG
2. Making authenticated API calls in API routes
3. Making an authenticated API request in `getServerSideProps`