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