The guide to set up NextJS Authentication and data fetching with AWS Amplify Gen2
Tags: amplify, nextjs, authentication, graphql
Published: 2023-11-20 20:42:30

Amplify, with its powerful suite of tools and seamless integration, truly revolutionizes the development experience.  From its intuitive interface to its extensive range of functionalities, Amplify stands for innovation, empowering creators to bring their visions to life with ease. Its ability to streamline workflows and accelerate the development process makes it a go-to choice for many, especially with the new Amplify Gen 2. 

> Amplify Gen 2: A new way of working that fully supports Typescript. 

Most of the instructions you can follow from:
https://docs.amplify.aws/gen2/start/quickstart/

_Always look first at this site for the latest updates._

## Contents

[Get started](#get-started)
[Create a data SCHEMA](#create-a-data-schema)
[Set up Authentication](#set-up-authentication)
[Nextjs APP Router and Authentication](#nextjs-app-router-and-authentication)
[Add data to your event page](#add-data-to-your-event-page)
[Test your app](#test-your-app)
[Deploy to the cloud](#deploy-to-the-cloud)
[Conclusion](#conclusion)

## Get started

Create a new installation of nextJS app. I am a fan of Tailwind and also to have a src folder. Of course you can change this. Replace APPNAME with the name of your app. 
```
npm create next-app@14 --APPNAME --typescript --eslint --no-app --src-dir --tailwind --import-alias '@/*'
```

In your project run:

```
npm create amplify
? Where should we create your project? (.) # press enter
```

### Set up your local environment
Follow [this](https://docs.amplify.aws/gen2/start/account-setup/) instruction to set up your local account and environment. Amplify Gen2 differs from Gen1 because you need a sandbox to develop locally. 

So make sure you run this:

```
amplify sandbox --profile <value>
```

## Create a data SCHEMA

Open up your created project in your favourite IDE and Open this folder and file:
`/amplify/data/resource.ts`

_We will create an app where events can be created and those events you can search, based on city that you are living in. You need to be logged in to be able to search the events. We have these fields: name, date, description and city._

Adjust the file with this content:

```ts
import { type ClientSchema, a, defineData } from '@aws-amplify/backend';

const schema = a.schema({
  Event: a
    .model({
      name: a.string(),
      date: a.datetime(),
      description: a.string(),
      city: a.string()
    })
    .authorization([a.allow.owner(),  a.allow.private()]),
});

export type Schema = ClientSchema<typeof schema>;

export const data = defineData({
  schema,
  authorizationModes: {
    defaultAuthorizationMode: 'userPool',
  },
});
```

## Set up Authentication
Open this file:
`/amplify/auth/resource.ts`

This file will deploy AWS Cognito resources. We will not make adjustments to that for now. But you can add for example MFA of add external identity providers like Amazon, Google or Facebook and so on. 

## Nextjs APP Router and Authentication
With Nextjs you can manage authentication on the client and server side. Setting authentication on the server is a little bit more complex but it will meet stronger security requirements. In this example we set up authentication on the server. 

### Set up Nextjs
Create folders and files in the **src folder** in NextJS, so you will have this structure:

```
-src
--app
----(auth)
------events
---------page.tsx
------layout.tsx
----login
------page.tsx
----layout.tsx
----page.tsx
----login
--components
----ConfigureAmplifyClientSide.ts
--utils
----server-utils.tsx
--middleware.ts

```

The **(auth)** folder will contain all folders and files for authenticated users. This is not needed, but helps me to have an better overview.

The event page.tsx will get all the events and show them on the page. 

`page.tsx`

```ts
const Events = () => {
  return (
    <div className="mx-auto max-w-4xl text-center">
      <h2 className="text-base font-semibold leading-7 text-indigo-600">
        Events
      </h2>
      <p className="mt-2 text-4xl font-bold tracking-tight text-gray-900 sm:text-5xl">
        Pricing plans for teams of&nbsp;all&nbsp;sizes
      </p>
    </div>
  );
};

export default Events;
```

`layout.tsx` in (auth) folder:
This is the root layout file for all the pages within the (auth) folder

```ts
"use client";
import { Header } from "@/components/Header";

export default function AuthLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <div className="bg-white">
      <Header />
      <main className="isolate">
        <div className="sm:pt-12">
          <div className="mx-auto max-w-7xl px-6 lg:px-8">{children}</div>
        </div>
      </main>
    </div>
  );
}
```

`page.tsx` in login folder:
This page will take care of the user authentication with AWS Amplify [Authenticator component](https://ui.docs.amplify.aws/react/connected-components/authenticator). The middleware file will redirect a user to the login page when there is no authenticated user. On the login page we will track the origin route and redirect the user to that page after logging in. 

```ts
"use client";

import { withAuthenticator } from "@aws-amplify/ui-react";
import "@aws-amplify/ui-react/styles.css";
import { useSearchParams } from "next/navigation";
import { redirect } from "next/navigation";

const Login = () => {
  const searchParams = useSearchParams();

  const origin = searchParams.get("origin");
  if (origin) {
    redirect(origin);
  }

  return <div>LOGIN</div>;
};

export default withAuthenticator(Login);

```

`layout.tsx` in app folder
The root layout is the standard file that is created when you ran **npm create next-app@14**. Please replace it with this content.

```ts

import ConfigureAmplifyClientSide from "@/components/ConfigureAmplifyClientSide";
import "./globals.css";
import { Amplify } from "aws-amplify";
import amplifyconfiguration from "@/amplifyconfiguration.json";

Amplify.configure(amplifyconfiguration);

import type { Metadata } from "next";

export const metadata: Metadata = {
  title: "Create Next App",
  description: "Generated by create next app",
};

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <body>
        <>
          <ConfigureAmplifyClientSide />
          {children}
        </>
      </body>
    </html>
  );
}

```

Create `ConfigureAmplifyClientSide.ts` in the components folder. This file will make sure that Ampify API and services are available on the server side. 

```ts
"use client";

import { Amplify } from "aws-amplify";
import config from "@amplifyconfiguration.json";

Amplify.configure(config, { ssr: true });

export default function ConfigureAmplifyClientSide() {
  return null;
}

```

`server-utils.tsx` in utils folder
This component is needed to have access to AWS Amplify services and API's on the server side. 

```ts
import { createServerRunner } from "@aws-amplify/adapter-nextjs";
import config from "@/amplifyconfiguration.json";

export const { runWithAmplifyServerContext } = createServerRunner({
  config,
});

```


`middleware.tsx` in app folder
Like mentioned before this component handle your routes before the actual page is loaded. If there is no auth session then redirect to login page and otherwise show requested page. 

In **matcher: ["/events/:path*"]** add the pages that you want to be authenticated. You can also do it the other way around. So have everything authenticated , except ...

```ts
import { fetchAuthSession } from "aws-amplify/auth/server";
import { NextRequest, NextResponse } from "next/server";
import { runWithAmplifyServerContext } from "./utils//server-utils";

export async function middleware(request: NextRequest) {
  const response = NextResponse.next();

  const authenticated = await runWithAmplifyServerContext({
    nextServerContext: { request, response },
    operation: async (contextSpec) => {
      try {
        const session = await fetchAuthSession(contextSpec);
        return session.tokens !== undefined;
      } catch (error) {
        console.log(error);
        return false;
      }
    },
  });

  if (authenticated) {
    return response;
  }

  const parsedURL = new URL(request.url);
  const path = parsedURL.pathname;

  return NextResponse.redirect(new URL(`/login?origin=${path}`, request.url));
}

export const config = {
  // matcher: [
  //   /*
  //    * Match all request paths except for the ones starting with:
  //    * - api (API routes)
  //    * - _next/static (static files)
  //    * - _next/image (image optimization files)
  //    * - favicon.ico (favicon file)
  //    */
  //   // "/((?!api|_next/static|_next/image|favicon.ico|login).*)",
  //   "/((?!api|_next/static|_next/image|favicon.ico|login|events|profile).*)",
  // ],
  // PUT MORE paths to protect to this array
  matcher: ["/events/:path*"],
};

```

## Add data to your event page
Open your root layout file of Nextjs and add these lines:

```ts
import { Amplify } from "aws-amplify";
import amplifyconfiguration from "@/amplifyconfiguration.json";

Amplify.configure(amplifyconfiguration);
```

Update a the event page where the data will be fetched and shown to the user:

```ts
// EventsList.tsx

"use client";
import type { Schema } from "../../../../amplify/data/resource";
import { useState, useEffect } from "react";
import { generateClient } from "aws-amplify/api";

const client = generateClient<Schema>();

export default function Events() {
  const [events, setEvents] = useState<Schema["Event"][]>([]);

  const fetchEvents = async () => {
    const { data: items, errors } = await client.models.Event.list();
    setEvents(items);
  };

  useEffect(() => {
    fetchEvents();
  }, []);

  return (
    <div>
      <ul
        role="list"
        className="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3"
      >
        {events.map((item, index) => (
          <li
            key={index}
            className="col-span-1 divide-y divide-gray-200 rounded-lg bg-white shadow"
          >
            <div className="flex w-full items-center justify-between space-x-6 p-6">
              <div className="flex-1 truncate">
                <div className="flex items-center space-x-3">
                  <h3 className="truncate text-sm font-medium text-gray-900">
                    {item.name}
                  </h3>
                  <span className="inline-flex flex-shrink-0 items-center rounded-full bg-green-50 px-1.5 py-0.5 text-xs font-medium text-green-700 ring-1 ring-inset ring-green-600/20">
                    {item.description}
                  </span>
                </div>
              </div>
            </div>
          </li>
        ))}
      </ul>
    </div>
  );
}

```

### Run your sandbox

```
npx amplify sandbox --profile YOURPROFILE --config-out-dir src
```

## Test your app
Now your backend is running, let's also start your front end application. 

```
npm run dev
```
Navigate to http://localhost:3000/events, you should notice that you are redirected to the login page. Create an account and log in. Then you will be redirected to the events page again and you should see events. 

_Be aware...you first need to create data before data will be shown in your app. You can create [pages in your NextJS app](https://docs.amplify.aws/gen2/build-a-backend/data/mutate-data/#create-an-item) or in the AWS Appsync Query Editor in the console._


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/desjy7vwgk966anwysdw.png)

Now you have evaluated the nextjs setup and have noticed that data is only accessible by authenticated users you can deploy your environment to the cloud. 


## Deploy to the cloud
Create a GIT repository in one of your favourite GIT providers.

Follow [this instruction](https://docs.amplify.aws/gen2/start/quickstart/#deploy-and-host-a-fullstack-branch) to connect your GIT repository to AWS Amplify and to deploy your backend stack. 


Here is my GIT repo on [GITHUB](https://github.com/rpostulart/amplify_nextjs_auth/tree/main)

## Conclusion
As you can see, the Amplify team delivers an outstanding result with Amplify Gen 2. NextJS is a great framework to run React applications as well client side as server side. With the last release of new Amplify features to support SSR it is now possible to have a secure solution to add authentication to your application. 

Happy developing!