ekim.dev
Factory Animate shelf
blog-thumbnail

Hey Devs !

As a front-end developer my nightmare was to handle the user authentication.

With NextAuth.js (I will call it auth.js since they are changing their name) the user authentication handling is much easier. Let's take a look at how we handle it with Next.js

Here are the steps that you need to follow;

Create a next app

yarn create next-app

Go inside your project and add auth.js

yarn add next-auth

To implement the auth.js, we need to create an api folder in our pages directory and create the [...nextauth].js

/pages
      /api
        /auth
          [...nextauth].js

In your 

pages/api/auth/[...nextauth].js
 create the default function and credential provider.

Since you can configure your NextAuth, we will create an authOptions object to store the configurations that we want aplly.

import NextAuth from "next-auth"
import CredentialsProvider from "next-auth/providers/credentials";
export const authOptions = {}
export default NextAuth(authOptions);

Now let's start to adding options

Since we are using custom backend, we are going to use CredentialsProvider and we will pass this to providers key. This key stores all of providers that you want to use in your application with their configurations.

providers: [
    CredentialsProvider({
      type: "credentials",

 credentials: {
        email: {
          label: "Email",
          type: "email",
        },
        password: { label: "Password", type: "password" },
      },
  ],


...rest 

If you want to use your own Sign In page, you do not need to pass email and password keys to credentials object. Anyway, we are going to use Auth.js's sign in page in this.

In this case we need the email address and password of user to authenticate so we pass email and password fields.

Now we all set in 

[...nextAuth].js
 file with its credentials object.

to fetch login data to your custom backend now we need to pass 

authorize
 function with your custom sign in api;

 async authorize(credentials) {
        const credentialDetails = {
          email: credentials.email,
          password: credentials.password,
        };

        const resp = await fetch(backendURL + "/auth/login", {
          method: "POST",
          headers: {
            Accept: "application/json",
            "Content-Type": "application/json",
          },
          body: JSON.stringify(credentialDetails),
        });
        const user = await resp.json();
        if (user.is_success) {
          return user;
        } else {
          console.log("check your credentials");
          return null;
        }
      },

In this case, 

backendURL
 is a constant of your server's ip and host, and the 
auth/login
 is the endpoint of your backend.

If your credentials are correct, the backend should response you a success message and you need to check it if its success or not.

For now the 

[...nextAuth].js
 is something like this:

import NextAuth from "next-auth/next";
import CredentialsProvider from "next-auth/providers/credentials";


const backendURL = process.env.NEXT_PUBLIC_BACKEND_URL;
export const authOptions = {
  providers: [
    CredentialsProvider({
      type: "credentials",
      credentials: {
        email: {
          label: "Email",
          type: "email",
        },
        password: { label: "Password", type: "password" },
      },
      async authorize(credentials) {
        const credentialDetails = {
          email: credentials.email,
          password: credentials.password,
        };

        const resp = await fetch(backendURL + "/auth/login", {
          method: "POST",
          headers: {
            Accept: "application/json",
            "Content-Type": "application/json",
          },
          body: JSON.stringify(credentialDetails),
        });
        const user = await resp.json();
        if (user.is_success) {
          console.log("nextauth daki user: " + user.is_success);

          return user;
        } else {
          console.log("check your credentials");
          return null;
        }
      },
    }),
  ],
};

export default NextAuth(authOptions);

since we are using JWT , you need to pass session object with strategy key as:

session: {
    strategy: "jwt",
    maxAge: 30 * 24 * 60 * 60, // 30 days
  },

you can pass a maxAge to store your user in the web browser as authenticated as you want, here it stores for 30 days.

Do not forget that JWT maxAge can be also defined in the backend so you should consider the backend JWT maxAge when setting this value.

Implementing the callbacks to use session in client-side

Now you can fetch user in backend and your response should have some information that will be used in the client side while fetching some APIs.

With the help of callbacks, we will persist the backend access token to token provided by auth.js right after signin.

For this, we need to pass callbacks object to 

[...nextauth].js
 file as:

callbacks: {
    jwt: async ({ token, user }) => {
      if (user) {
        token.email = user.data.auth.email;
        token.username = user.data.auth.userName;
        token.userType = user.data.auth.userType;
        token.accessToken = user.data.auth.token;
      }

      return token;
    },
}

In this case, from the response provided by backend, user object has various values like email,userName,userType and token.

In jwt object we are calling the async function that stores our response in token.

After that, from frontend, we need to add the token to cookies in user session, like this :

callbacks: {
    jwt: async ({ token, user }) => {
      if (user) {
        token.email = user.data.auth.email;
        token.username = user.data.auth.userName;
        token.user_type = user.data.auth.userType;
        token.accessToken = user.data.auth.token;
      }

      return token;
    },
    session: ({ session, token, user }) => {
      if (token) {
        session.user.email = token.email;
        session.user.username = token.userName;
        session.user.accessToken = token.accessToken;
      }
      return session;
    },
  },

with the help of session callback, now we can use the useSession hook provided by auth.js in the client-side to get information that we pass to session object.

Before going into the client side, lets check your 

[...nextAuth].js
:

import NextAuth from "next-auth/next";
import CredentialsProvider from "next-auth/providers/credentials";

const backendURL = process.env.NEXT_PUBLIC_BACKEND_URL;
export const authOptions = {
session: {
    strategy: "jwt",
    maxAge: 30 * 24 * 60 * 60, // 30 days
  },
  providers: [
    CredentialsProvider({
      type: "credentials",
      credentials: {
        email: {
          label: "Email",
          type: "email",
        },
        password: { label: "Password", type: "password" },
      },
      async authorize(credentials) {
        const credentialDetails = {
          email: credentials.email,
          password: credentials.password,
        };

        const resp = await fetch(backendURL + "/auth/login", {
          method: "POST",
          headers: {
            Accept: "application/json",
            "Content-Type": "application/json",
          },
          body: JSON.stringify(credentialDetails),
        });
        const user = await resp.json();
        if (user.is_success) {
          console.log("nextauth daki user: " + user.is_success);

          return user;
        } else {
          console.log("check your credentials");
          return null;
        }
      },
    }),
  ],
callbacks: {
    jwt: async ({ token, user }) => {
      if (user) {
        token.email = user.data.auth.email;
        token.username = user.data.auth.userName;
        token.user_type = user.data.auth.userType;
        token.accessToken = user.data.auth.token;
      }

      return token;
    },
    session: ({ session, token, user }) => {
      if (token) {
        session.user.email = token.email;
        session.user.username = token.userName;
        session.user.accessToken = token.accessToken;
      }
      return session;
    },
  },
};

export default NextAuth(authOptions);

Use the session in client side !

Lets start wrapping our application with SessionProvider;

Move to your _app.js file in the route;

/pages
       _app.js

and import SessionProvider from next-auth

import { SessionProvider } from "next-auth/react";

As well as you import the session provider, do not forget to pass session in to the page props and wrap your application with SessionProvider like this in your 

_app.js
 file;

import { SessionProvider } from "next-auth/react";

function MyApp({ Component, pageProps: { session, ...pageProps } }) {
  return (
    <SessionProvider session={session}>
          <Component {...pageProps} />
    </SessionProvider>
  );
}
export default MyApp;

After that everything is simple, just import 

useSession()
hook from next-auth and handle any session information that you passed in the session callback function in 
[...nextauth].js
 file.

import { useSession} from "next-auth/react";
const { data, status } = useSession();

Now you can use any data that you passed in session callback function by using 

data
 object from useSession().

In this example we can access the user access token which we got from server as : 

session.user.accessToken

Also, you can check if the user is authenticated in the serverside, so you may want to protect your pages.

In your component, you may use session in getServerSideProps function as:

import getSession to use session in serverside from :

import {getSession } from "next-auth/react";

export async function getServerSideProps(context) {

  const session = await getSession(context);
  if (!session) {
    //if not exists, return a temporary 302 and replace the url with the given in Location.
    context.res.writeHead(302, { Location: "/signin" });
    context.res.end();

    //do not return any session.
    return { props: {} };
  }
}

And this is a simple use of credentials provider from Auth.js