DEV Community

Cover image for NextAuth.js / Auth.js credential authentication with methods you need !
Ekim Cem Ülger
Ekim Cem Ülger

Posted on

NextAuth.js / Auth.js credential authentication with methods you need !

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

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);
Enter fullscreen mode Exit fullscreen mode

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

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


Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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
  },

Enter fullscreen mode Exit fullscreen mode

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

Enter fullscreen mode Exit fullscreen mode

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

Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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

and import SessionProvider from next-auth

import { SessionProvider } from "next-auth/react";
Enter fullscreen mode Exit fullscreen mode

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;

Enter fullscreen mode Exit fullscreen mode

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

Enter fullscreen mode Exit fullscreen mode

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: {} };
  }
}

Enter fullscreen mode Exit fullscreen mode

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

Do let me know if you have any suggestions or questions in the commends !

Top comments (10)

Collapse
 
saitgungor profile image
Sait

Quite useful content and very clear explanation, thanks @ekimcem

Collapse
 
sergo profile image
Sergo

Thank you, @ekimcem.

Collapse
 
meanurag profile image
Anurag Parmar

One of the best articles on this topic.

Collapse
 
haniframadhani profile image
muhammad hanif ramadhani

what does the expected fetch response look like? and what does should return after fetch? my response look like this

{
  "status": 200,
  "message": "success",
  "accessToken": "jwt token"
}
Enter fullscreen mode Exit fullscreen mode
Collapse
 
ekimcem profile image
Ekim Cem Ülger

Actually it depends what you want to return when the user signin.

Generally I return the user data like username, email, user type, user id, etc.

When the user signs in you will need some of the user's properties in order to display relevant data in your app.

so it depends on your app and backend structure. @haniframadhani

Collapse
 
derick1530 profile image
Derick Zihalirwa

I have my custom signin page, How can I adjust your code in my code ?
@sait

Collapse
 
ekimcem profile image
Ekim Cem Ülger

For custom signin page, you should add "pages"object into your nextauth config;
like in the example

 pages: {
    signIn: '/auth/signin',
  }
Enter fullscreen mode Exit fullscreen mode

and then assign your custom signin page route into the signIn.

The thing is you need to import signin function from nextauth and provide the required credentials into that function.

Collapse
 
derick1530 profile image
Derick Zihalirwa

Thanks

Collapse
 
derick1530 profile image
Derick Zihalirwa

Can you make an updated version of this code in TS?

Collapse
 
ekimcem profile image
Ekim Cem Ülger

Will add it soon.