DEV Community

Cover image for The guide to set up NextJS Authentication and data fetching with AWS Amplify Gen2
rpostulart for AWS Community Builders

Posted on • Edited on

The guide to set up NextJS Authentication and data fetching with AWS Amplify Gen2

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
Create a data SCHEMA
Set up Authentication
Nextjs APP Router and Authentication
Add data to your event page
Test your app
Deploy to the cloud
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 '@/*'


Enter fullscreen mode Exit fullscreen mode

In your project run:



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


Enter fullscreen mode Exit fullscreen mode

Set up your local environment

Follow this 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>


Enter fullscreen mode Exit fullscreen mode

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:



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',
  },
});


Enter fullscreen mode Exit fullscreen mode

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



Enter fullscreen mode Exit fullscreen mode

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



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;


Enter fullscreen mode Exit fullscreen mode

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



"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>
  );
}


Enter fullscreen mode Exit fullscreen mode

page.tsx in login folder:
This page will take care of the user authentication with AWS Amplify Authenticator component. 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.



"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);



Enter fullscreen mode Exit fullscreen mode

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.




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>
  );
}



Enter fullscreen mode Exit fullscreen mode

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



"use client";

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

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

export default function ConfigureAmplifyClientSide() {
  return null;
}



Enter fullscreen mode Exit fullscreen mode

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



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

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



Enter fullscreen mode Exit fullscreen mode

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 ...



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*"],
};



Enter fullscreen mode Exit fullscreen mode

Add data to your event page

Open your root layout file of Nextjs and add these lines:



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

Amplify.configure(amplifyconfiguration);


Enter fullscreen mode Exit fullscreen mode

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



// 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>
  );
}



Enter fullscreen mode Exit fullscreen mode

Run your sandbox



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


Enter fullscreen mode Exit fullscreen mode

Test your app

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



npm run dev


Enter fullscreen mode Exit fullscreen mode

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 or in the AWS Appsync Query Editor in the console.

Image description

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 to connect your GIT repository to AWS Amplify and to deploy your backend stack.

Here is my GIT repo on GITHUB

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!

Top comments (1)

Collapse
 
crhis35 profile image
Crhistian J. Caraballo • Edited

Do you know how I can combine with this middleware:


import createMiddleware from 'next-intl/middleware';

export default createMiddleware({
  // A list of all locales that are supported
  locales: ['en', 'de'],

  // Used when no locale matches
  defaultLocale: 'en'
});

export const config = {
  // Match only internationalized pathnames
  matcher: ['/', '/(de|en)/:path*']
};

Enter fullscreen mode Exit fullscreen mode