A Guide to user authorization in Nextjs

akash133

Akash

Posted on October 8, 2021

A Guide to user authorization in Nextjs

Hello everyone, This is Akash! Things are getting better everyday and I hope everyone is good. I am working as a MERN stack developer in TCS. In recent times I came across Nextjs and it is one of the best frameworks available for developing web applications. As the official website says, it is React framework
for production, which gives all the features you need for production like hybrid static & server rendering, TypeScript support, smart bundling, route pre-fetching etc. In this blog I would like to show you how to use AWS Amplify with Next and how to authorize users.

Creating new Next app

Creating a new next app is as simple as executing a command in the terminal. For starters, make sure you have node and npx installed in your pc.

$ npx create-next-app nextapp
Enter fullscreen mode Exit fullscreen mode

Here "nextapp" is the name of the app. Now open this new app in your preferred code editor.

Start Next server

Once you open the nextapp directory in any editor, you can observe various files and directories. We have to create and write code for our web pages in the “pages” directory. Our entire website starts at "pages/_app.js". Execute the below command to start the server.

$ npm run dev
Enter fullscreen mode Exit fullscreen mode

This command starts the server on port 3000. You can open any browser and go to http://localhost:3000 for accessing your app.

The content of the page you see at http://localhost:3000 is in pages/index.js file. "pages" help us create pages for our app and each of them is associated with a route based on its file name. For example, content of pages/index.js can be seen at http://localhost:3000/ and pages/about.js at http://localhost:3000/about etc. To know more about pages, refer this

Initialising Amplify

AWS Amplify is a set of tools and services that can be used together, or on their own, to help front-end web and mobile developers build scalable full stack applications, powered by AWS. You can install amplify cli using npm.

$ npm install -g @aws-amplify/cli
Enter fullscreen mode Exit fullscreen mode

Now initialise amplify at the root of our app and stick on to the default configurations as mentioned.

$ amplify init

? Enter a name for the project nextapp
The following configuration will be applied:

Project information
| Name: nextapp
| Environment: dev
| Default editor: Visual Studio Code
| App type: javascript
| Javascript framework: react
| Source Directory Path: src
| Distribution Directory Path: build
| Build Command: npm run-script build
| Start Command: npm run-script start

? Initialize the project with the above configuration? Yes
? Select the authentication method you want to use: AWS profile
? Please choose the profile you want to use default
Enter fullscreen mode Exit fullscreen mode

After that, add authentication service

$ 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.
Enter fullscreen mode Exit fullscreen mode

Now deploy the service

$ amplify push --y
Enter fullscreen mode Exit fullscreen mode

Creating pages

Install the packages below as we use them in our app in the further steps.

$ npm install aws-amplify @aws-amplify/ui-react @emotion/css
Enter fullscreen mode Exit fullscreen mode

In our app, we are going to maintain a state to store the authentication status of a user. Inorder to access that state information in our pages and components, we will use useContext react hook.
For that, create a directory libs and file contextLib.js in libs. Write below code in contextLib.js

// libs/contextLib.js
import { useContext, createContext } from "react";

export const AppContext = createContext(null);

export function useAppContext() {
  return useContext(AppContext);
}
Enter fullscreen mode Exit fullscreen mode

Here we are creating and exporting a context object called AppContext, a function which will return a useContext function. These two will be imported in subsequent files. Lets update pages/_app.js.

Replace content of _app.js with below

import {useState, useEffect} from 'react'
import Amplify, { Auth } from 'aws-amplify'
import config from '../src/aws-exports'
import Link from 'next/link'
import {css} from '@emotion/css'
import { AppContext } from "../libs/contextLib";
import '../styles/globals.css'

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

export default function MyApp({ Component, pageProps }) {
 const [isAuthenticated, setIsAuthenticated] = useState(false)

 useEffect(() => {
   Auth.currentAuthenticatedUser()
     .then(() => {
       setIsAuthenticated(true)
     })
     .catch(err => setIsAuthenticated(false))
 }, [])

 console.log('Auth: ', isAuthenticated)
 return (
   <div>
     <nav className={navStyle}>
       <Link href="/">
         <span className={linkStyle}>About</span>
       </Link>
       <Link href="/home">
         <span className={linkStyle}>Home</span>
       </Link>
       {
         !isAuthenticated &&
         <Link href="/login">
           <span className={linkStyle}>Login</span>
         </Link>
       }
     </nav>
     <AppContext.Provider value={{
       isAuthenticated,
       setIsAuthenticated
     }}>
       <Component {...pageProps} />
     </AppContext.Provider>
   </div>
 )
}

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

const navStyle = css`
 float: right;
 margin-top: 10px;
`
Enter fullscreen mode Exit fullscreen mode

There are a few things to notice here. AppContext is imported from libs/contextLib.js. We are creating "isAuthenticated" state and "setIsAuthenticated" function using useState react hook. We also use Amplify from 'aws-amplify' to configure our app and enable Amplify SSR.

The "Component" is wrapped with AppContext. isAuthenticated, setIsAuthenticated are passed to the value of AppContext Provider. With this we can access those fields in our components and pages.

Next let’s create two components, AuthenticatedRoute and UnauthenticatedRoute, inside “components” directory at root of app.

// components/AuthenticatedRoute.js
import { useAppContext } from "../libs/contextLib"
import { useRouter } from "next/router";

const AuthenticatedRoute = (WrappedComponent) => {
   return (props) => {
       const { isAuthenticated } = useAppContext();
       const Router = useRouter();
       if (typeof window !== "undefined") {
           if (!isAuthenticated) {
               Router.push("/login");
               return null;
           }
           return <WrappedComponent {...props} />;
       }
       // If we are on server, return null
       return null;
   };
};

export default AuthenticatedRoute;

Enter fullscreen mode Exit fullscreen mode
// components/UnauthenticatedRoute.js
import { useAppContext } from "../libs/contextLib"
import { useRouter } from 'next/router'

const UnauthenticatedRoute = (WrappedComponent) => {
   return (props) => {
       const { isAuthenticated } = useAppContext();
       const Router = useRouter();
       if (typeof window !== "undefined") {
           if (isAuthenticated) {
               Router.replace("/home");
               return null;
           }
           return <WrappedComponent {...props} />;
       } 
       // If we are on server, return null
       return null;
   };
};

export default UnauthenticatedRoute
Enter fullscreen mode Exit fullscreen mode

In these components, we imported useAppContext from libs and we are using isAuthenticated state to determine whether the user is authenticated or not. Based on the isAuthenticated value, we return the page, which the user wants to access or redirect them to respective default pages. These two components are exported such that pages are wrapped around them.

replace pages/index.js with below

// pages/index.js
import Link from 'next/link'
import AuthenticatedRoute from '../components/AuthenticatedRoute'
import styles from '../styles/Home.module.css'

function Index() {
 return (
   <div className={styles.container}>
     <h3>
       Index
     </h3>
   </div>
 )
}

export default AuthenticatedRoute(Index)
Enter fullscreen mode Exit fullscreen mode

Home.module.css is not modified.

Create home.js and login.js in pages.

// pages/home.js
import AuthenticatedRoute from "../components/AuthenticatedRoute";
import { AmplifySignOut } from '@aws-amplify/ui-react'
import { useAppContext } from "../libs/contextLib";

function Home(props) {
   const { setIsAuthenticated } = useAppContext();
   return (
       <div>
           <h1>Homeee!!!</h1>
           <AmplifySignOut style={{
               width: 400
           }} onClick={() => {
               setIsAuthenticated(false)
           }}/>
       </div>
   )
}

export default AuthenticatedRoute(Home);
Enter fullscreen mode Exit fullscreen mode
// pages/login.js
import { useState, useEffect } from 'react'
import { Auth } from 'aws-amplify'
import { withAuthenticator } from '@aws-amplify/ui-react'
import { useAppContext } from "../libs/contextLib";
import UnauthenticatedRoute from '../components/UnauthenticatedRoute';

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

export default withAuthenticator(UnauthenticatedRoute(Login))
Enter fullscreen mode Exit fullscreen mode

It can be observed that home.js is exporting the Home component wrapped around AuthenticatedRoute and the same with index.js as well. login.js is exporting a Login component wrapped around UnauthenticatedRoute. Also, we are using withAuthenticator from '@aws-amplify/ui-react' so that we can signup and signin users. useEffect() in login.js is checking if any user is authenticated and based on that it is changing the isAuthenticated value using setIsAuthenticated function.

Now restart the server to see all the changes that are made. Trying to access "home" or "about" without authentication is not possible because those are Authenticated routes. This provides page level authorizations.

Conclusion

Check out the complete source code for this blog here.
Next is getting more attention these days and it is time for us to dig deeper and know its roots. In this blog we saw how to authorize users in Nextjs. Checkout more about Next in their official website and make the best out of it. I’m always up for any discussions and collaborations.

Until the next time, Stay safe!!!

💖 💪 🙅 🚩
akash133
Akash

Posted on October 8, 2021

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related