0

I am attempting to implement NextAuth.js in my NextJs app. There is a backend ExpressJs server responsible for implementing JWT Tokens and login details. I am following the official documentation (credentials sign in, jwt callback, session callback) and numerous examples, but for some reason, I am unable to set the session properly. I am using a custom SignIn page,

The signIn event logs the correct user details, but inside the jwt and the session callbacks, they don't. I am confused why would it happen like this. The session object is stored appropriately, and the it helps me to identify if the user has logged in or not, but I am unable to figure out which user.

I am using signIn method to Sign in the user.

Here is my code from /pages/api/[...nextauth.ts]

import NextAuth, { Session, User } from "next-auth"
import CredentialsProvider from "next-auth/providers/credentials";
import { verifyOTP } from "@/lib/otp";
import { JWT } from "next-auth/jwt";

export default NextAuth({
  pages: {
    signIn: '/signup',
    newUser: '/signup',
    error: '/signup'
  },
  debug: true,
  secret: "SOME_SECRET",
  providers: [
    CredentialsProvider({
      credentials: {
        username: {label: "username", type: "text", placeholder: "markandre"},
        phone: { label: "Phone ", type: "text", placeholder: "+1(789)345-765490" },
        otp: {  label: "OTP", type: "text" }
      },
      authorize: async (credentials, _req): Promise<User|undefined> => {
        try {
          const res = await verifyOTP(credentials!.phone,
            credentials!.otp, credentials?.username);
          console.log("USER***AUTH");
          console.log(res);
          if (res.result === "approved") {
            return {
              id: res.user.id,
              email: res.user.email,
              name: res.user.name,
              token: res.user.token
            };
          }
        } catch (e: any) {
          const errorMessage = e.response.data.message;
          throw new Error(errorMessage);
        }
      }
    })
  ],
  session: {
    strategy: 'jwt',
    maxAge: 3 * 60 * 60, // 3 hours
  },
  callbacks: {
    jwt: async (token: JWT, user: User) => {
      console.log("JWT ASYNC")
      console.log('user', user); // this always shows `undefined`
      if (user) {
        token.accessToken = user.token
      }

      return token;
    },
    session:async (session: Session, token: JWT, user: User) => {
      console.log("SESSION");
      console.log('Session', session);
      console.log('User', user); // this is always shown as `undefined`
      console.log('session.user', session.user); // this is also always `undefined`
      console.log('token', token);

      if (token) {
        session.accessToken = token.accessToken;
      }
      return session;
    }
  },
  events: {
    signIn: async (message) => {
      console.log('signIn', message); // This shows the actual user returned from authorize
    },
    signOut: async (message) => {
      console.log('signOut', message);
    },
    createUser: async(message) => {
      console.log('createUser', message);
    },
    updateUser: async(message) => {
      console.log('updateUser', message);
    },
    linkAccount: async(message) => {
      console.log('linkAccount',message);
    },
    session: async(message) => {
      console.log('session', message);
    }
  }
})

My /pages/signup.tsx looks like below


import { getOTP } from '@/lib/otp';
import { LockClosedIcon } from '@heroicons/react/solid';
import React, { useState } from "react";
import { getCsrfToken } from "next-auth/react";


export async function getServerSideProps(context) {
  return {
    props: {
      csrfToken: await getCsrfToken(context),
    },
  }
}

export default function SignUp({ csrfToken }) {
    return (
        <>
        <form className="mt-8 space-y-6" onSubmit={signUp} action='/api/auth/callback/credentials' method='POST'>
            <input name="csrfToken" type="hidden" defaultValue={csrfToken} />

            // ...
            // ...
        </form>
        <>

})
}

My /pages/_app.tsx


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


import '../styles/global.css';

// Use the <SessionProvider> to improve performance and allow components that call
// `useSession()` anywhere in your application to access the `session` object.
export default function App({ Component, pageProps }: AppProps) {
  return (
    <SessionProvider
      // Provider options are not required but can be useful in situations where
      // you have a short session maxAge time. Shown here with default values.
      session={pageProps.session}
    >
      <Component {...pageProps} />
    </SessionProvider>
  )
}

Second part of question

Assuming I am able to get the session implemented correctly, if I have to make authenticated call to the backend API server (by sending the token in header), what's the best way to do it? I have seen some folks intercepting axios, while some other patch the fetch function and let it take session and add the headers appropriately.

The console logs are


USER***AUTH
{
  user: {
    id: 1,
    username: null,
    phone: '+12345678901',
    phoneVerified: true,
    email: temp@email.org,
    emailVerified: false,
    active: true,
    token: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXV',
    createDate: '2022-01-31T12:53:43.535Z',
    updateDate: '2022-01-31T17:45:50.985Z'
  },
  result: 'approved'
}
JWT ASYNC
user undefined
signIn {
  user: {
    id: 1,
    email: 'temp@email.org',
    name: '+12345678901',
    token: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXV'
  },
  account: {
    providerAccountId: 1,
    type: 'credentials',
    provider: 'credentials'
  }
}
JWT ASYNC
user undefined
SESSION
Session {
  session: {
    user: { name: undefined, email: undefined, image: undefined },
    expires: '2022-01-31T20:47:43.625Z'
  },
  token: {
    token: {
      token: [Object],
      user: [Object],
      account: [Object],
      isNewUser: false,
      iat: 1643651263,
      exp: 1646243263,
      jti: '886c4407-53a5-4527-8247-21b0713a4a9a'
    }
  }
}
User undefined
session.user undefined
token undefined
pr4n
  • 2,918
  • 3
  • 30
  • 42

1 Answers1

0

So, I found this answer, and I realised I am on v4 of nextAuth.js

Version 4 of NextAuth brings some changes to callbacks shown above. Now there is only one argument assigned to jwt and session functions. However, you can destructure it to separate variables. Rest of the code stays the same as before.

As soon as I changed by functions to jwt: async ({token, user}) => { and session:async ({session, token, user}) I was able to get the appropriate session variables.

pr4n
  • 2,918
  • 3
  • 30
  • 42