NextJS User Login & Registration (AWS Cognito)
Tags: nextjs, node, tutorial, aws
Published: 2023-05-28 21:19:36

The purpose of this project is to build a boilerplate for NextJS which will allow me to quickly start with a base which already has a login and register system built. Sounds great hey! P.S this project will be available as an open source on Github and any contributions are more than welcome!

---

## Getting started

To begin with I started by creating just a basic NextJS using the NPX command.

```
npx create-next-app next-auth-boilerplate
```

I created the project using typescript, eslint and left the rest as default. (I'm not using tailwind in this example).

After doing so I opened up the folder in VS Code, you can also do `code next-auth-boilerplate` as well to make it quicker 😉

---

## Housekeeping

Of course, we want to make sure that everything is pretty and follows standards in the code so I went ahead and installed prettier, commitlint and husky. (Not going to go into depth about these here for this article but you can view the git repo to view all the config files I setup etc...)

```
npm i -D husky @commitlint/cli @commitlint/config-conventional prettier
```

---

## File structure 

So the structure I went for, is as follows...

(I have only included files and folders I have made so config files, package.json files etc.. should be there regardless)

```
├── layouts
│   ├── Footer.tsx
│   ├── Head.tsx
│   └── Header.tsx
├── pages
│   ├── _app.tsx
│   ├── _document.tsx
│   ├── register.tsx
│   └── login.tsx
├── styles
│   ├── Global.scss
│   └── Reset.scss
├── utils
│   ├── Global.scss
│   └── Reset.scss
├── public
├── templates
└── .env
```

---

## Prepping for AWS Cognito

So I am going to use AWS Cognito to handle the user database. AWS Cognito has a very generous free tier which allows for 50,000 monthly active users for free! Seeing as that is quite a lot, I don't think I'll be paying a single penny on any of my projects for years to come... It's a good, safe option if you are looking for something and just makes it so much easier to build login systems.

I'm starting off by installing the Amplify SDK..

```
npm i @aws-amplify/ui-react aws-amplify
```

After doing this, I am then going to go into my `.env` file and add the following variables.

```
AWS_COGNITO_REGION='{ENTER YOUR REGION e.g eu-west-1}'
AWS_COGNITO_POOL_ID='{ENTER POOL ID}'
AWS_COGNITO_APP_CLIENT_ID='{ENTER CLIENT ID}'
```

So head on over to Cognito and setup a user pool. Notes on each of these variables...

AWS_COGNITO_REGION - You should be able to see this in the pool ID in the overview section of the user pool.

AWS_COGNITO_POOL_ID - This should be visible after clicking on your user pool in the overview.

AWS_COGNITO_APP_CLIENT_ID - You can find this in your user pool overview under the tab "App integration" and scroll down to the bottom and it should be there.

Setting up a Cognito User Pool for the first time is a struggle, but there are plenty of guides from AWS to assist with this. If you want me to adapt this tutorial to include setting up Cognito, let me know in the comments!

We just need to do a slight change to the `next.config.js` file to include your ENV to make it available where required. In your module exports add the following...

```
env: {
    AWS_COGNITO_REGION: process.env.AWS_COGNITO_REGION,
    AWS_COGNITO_POOL_ID: process.env.AWS_COGNITO_POOL_ID,
    AWS_COGNITO_APP_CLIENT_ID: process.env.AWS_COGNITO_APP_CLIENT_ID,
},
```

Finally, I'm going to just setup the Amplify config... Create a file in `/utils/aws/Amplify.ts` and then copy in the following code to construct a Amplify Auth instance...

```
import { Amplify } from 'aws-amplify';

Amplify.configure({
    Auth: {
        region: process.env.AWS_COGNITO_REGION,
        userPoolId: process.env.AWS_COGNITO_POOL_ID,
        userPoolWebClientId: process.env.AWS_COGNITO_APP_CLIENT_ID,
    },
});
```

and then go into `/pages/_app.tsx` and add an import statement at the top as follows...

```
import '@/utils/aws/Amplify';
import type { AppProps } from 'next/app';

export default function App({ Component, pageProps }: AppProps) {
    return <Component {...pageProps} />;
}
```

---

## Creating the login/register page

Next is to create the login and signup page. In `/pages/login.tsx` copy in this premade code from AWS with a few modifications...

```
import { Authenticator, useAuthenticator } from '@aws-amplify/ui-react';
import '@aws-amplify/ui-react/styles.css';
import { Auth } from 'aws-amplify';
import { useRouter } from 'next/router';
import { useEffect } from 'react';

const Login = () => {
    const router = useRouter();
    const { user } = useAuthenticator((context) => [context.user]);

    useEffect(() => {
        const checkUser = async () => {
            try {
                const user = await Auth.currentAuthenticatedUser();
                if (user) {
                    router.push('/dashboard');
                }
            } catch (error) {
                // User is not logged in
            }
        };
        checkUser();
    }, [router, user]);

    return <Authenticator />;
};

export default Login;
```

---

## Creating an authentication HOC

Now we are going to go ahead and create HOC... (Basically, just a wrapper).

Create a file as such `/utils/withAuth.tsx` and copy and paste the following code.

```
import { useAuthenticator } from '@aws-amplify/ui-react-core';
import { Auth } from 'aws-amplify';
import { useRouter } from 'next/router';
import { useEffect } from 'react';

const withAuth = (WrappedComponent: any) => {
    const WrapperComponent = (props: any) => {
        const router = useRouter();
        const { user } = useAuthenticator((context) => [context.user]);

        useEffect(() => {
            const checkAuth = async () => {
                try {
                    await Auth.currentAuthenticatedUser();
                } catch (err) {
                    router.push('/login');
                }
            };

            checkAuth();
        }, [router, user]);

        return <WrappedComponent {...props} />;
    };

    return WrapperComponent;
};

export default withAuth;
```

Basically, what we are doing here is adding a default useEffect to monitor if the users status changes, i.e logged in/logged out and then redirect them to the login page if they need to log in again. We are using the Amplify Authenticator context here to manage states.

---

## Add Authenticator Provider Context

Talking about context's, we need to wrap the Authenticator Provider to the `/pages/_app.tsx` file. And update to look like the following code 👀..

```
import '@/utils/aws/Amplify';
import { Authenticator } from '@aws-amplify/ui-react';
import type { AppProps } from 'next/app';

export default function App({ Component, pageProps }: AppProps) {
    return (
        <Authenticator.Provider>
            <Component {...pageProps} />
        </Authenticator.Provider>
    );
}
```

---

## Protected pages

Finally, all you have to do now is add all the protected pages you wish but make sure that your export function has the `withAuth()` HOC wrapped around it! Here's an example of the dashboard...

```
import withAuth from '@/utils/withAuth';
import { Auth } from 'aws-amplify';

const Dashboard = () => {
    return (
        <div>
            <h1>Dashboard Page</h1>
            <button onClick={() => Auth.signOut()}>Sign out</button>
        </div>
    );
};

export default withAuth(Dashboard);
```

---

## Find the code on Github :)

[Sphinx ByDigital](https://github.com/by-Digital/Sphinx/tree/auth-tutorial)

Hope you enjoyed 🎉