NextJS and NextAuth session user object getting lost due to [...nextauth.ts] getting triggered to be recompiled
Asked Answered
B

2

5

I am learning NextJS and NextAuth and have implemented a Credentials sign in with my own login page and it is working where the session object contains my user model (at the moment contains everything including the password but obviously it won't stay like that).

I can refresh the page and the session is maintained, however if I leave for a minute or two and then refresh the user object in my session becomes the default even though my session is not supposed to expire until next month.

Below is my [...nextauth.tsx] file

import NextAuth, {NextAuthOptions} from 'next-auth'
import Providers from 'next-auth/providers'
import { PrismaClient } from '@prisma/client'
import {session} from "next-auth/client";

let userAccount = null;

const prisma = new PrismaClient();

const providers : NextAuthOptions = {
    site: process.env.NEXTAUTH_URL,
    cookie: {
        secure: process.env.NODE_ENV && process.env.NODE_ENV === 'production',
    },
    redirect: false,
    providers: [
        Providers.Credentials({
            id: 'credentials',
            name: "Login",
            async authorize(credentials : any) {
                const user = await prisma.users.findFirst({
                    where: {
                        email: credentials.email,
                        password: credentials.password
                    }
                });

                if (user !== null)
                {
                    userAccount = user;
                    return user;
                }
                else {
                    return null;
                }
            }
        })
    ],
    callbacks: {
        async signIn(user, account, profile) {
            console.log("Sign in call back");
            console.log("User Is");
            console.log(user);
            if (typeof user.userId !== typeof undefined)
            {
                if (user.isActive === '1')
                {
                    console.log("User credentials accepted")
                    return user;
                }
                else
                {
                    return false;
                }
            }
            else
            {
                console.log("User id was not found so rejecting signin")
                return false;
            }
        },
        async session(session, token) {
            //session.accessToken = token.accessToken;
            if (userAccount !== null)
            {
                session.user = userAccount;
            }
            console.log("session callback returning");
            console.log(session);
            return session;
        },
        /*async jwt(token, user, account, profile, isNewUser) {
            console.log("JWT User");
            console.log(user);
            if (user) {
                token.accessToken = user.token;
            }
            return token;
        }*/
    }
}

const lookupUserInDb = async (email, password) => {
    const prisma = new PrismaClient()
    console.log("Email: " + email + " Password: " + password)
    const user = await prisma.users.findFirst({
        where: {
            email: email,
            password: password
        }
    });
    console.log("Got user");
    console.log(user);
    return user;
}

export default (req, res) => NextAuth(req, res, providers).

I trigger the signin from my custom form as below

signIn("credentials", {
            email, password, callbackUrl: `${window.location.origin}/admin/dashboard`, redirect: false }
        ).then(function(result){
            if (result.error !== null)
            {
                if (result.status === 401)
                {
                    setLoginError("Your username/password combination was incorrect. Please try again");
                }
                else
                {
                    setLoginError(result.error);
                }
            }
            else
            {
                router.push(result.url);
            }
            console.log("Sign in response");
            console.log(result);
        });

Signin is imported from next-auth/client

My _app.js is below:

export default function Blog({Component, pageProps}) {
    return (
        <Provider session={pageProps.session}>
            <Component className='w-full h-full' {...pageProps} />
        </Provider>
    )

}

Then the page where it redirects to after signin has the below: (Not sure if this actually does anything other than fetch the active session so I can reference it from the frontend)

const [session, loading] = useSession()

When I signin, the callback for session in [...nextauth.tsx] returns the following:

session callback returning
{
  user: {
    userId: 1,
    registeredAt: 2021-04-21T20:25:32.478Z,
    firstName: 'Some',
    lastName: 'User',
    email: 'someone@example',
    password: 'password',
    isActive: '1'
  },
  expires: '2021-05-23T17:49:22.575Z'
}

Then for some reason the terminal that is running npm run dev from inside PhpStorm then outputs

event - build page: /api/auth/[...nextauth]
wait  - compiling...
event - compiled successfully

But I didn't change anything, and even if I did, surely me making a change to the app, shouldn't trigger the session to be removed, yet straight after this, the session callback then returns the following:

session callback returning
{
  user: { name: null, email: '[email protected]', image: null },
  expires: '2021-05-23T17:49:24.840Z'
}

So I'm a little confused, it seems like that maybe my code is working, but maybe PhpStorm is triggering recompile and then the session is cleared, but as I said above, surely making a change and the recompiled version shouldn't trigger the session to be modified.

Update

I have done a test where I did a build and started is a production version, and I can refresh the page as much as I want and the session is maintained, so I've proved that my code works fine. So it looks like it's something do with PhpStorm determining something has changed and doing a recompile even though nothing has changed.

Billfold answered 23/4, 2021 at 17:53 Comment(4)
it's not the IDE that is rebuilding the app, the build process you start with npm run dev that is watching your files for changes is triggered for some reason. The only way the IDE can affect this is saving the files, but, according to you, you don't change anything. Note that the IDE normally auto-saves files on losing focus, but those files that were not changed won't be saved.Kike
Hi @lena, I did just find that out myself not too long ago, I'm not changing any files, my browser maintains focus the whole time and none of the file are opened anywhere else (I have this on both Windows and MacOS so its not an OS issue)Billfold
it can hardly be the IDE then. Under no circumstances it re-saves unchanged files (even if you invoke Save All explicitly), neither it changes them itselfKike
I understand its not the IDE now, that was only a guess until I figured out how next dev command works. Just as a test I ran npm run dev from a terminal outside the IDE and the IDE closed and I am still having the issue. So I am having a problem with next dev command, it shouldn't be compiling and even if it did, surely a recompile shouldn't trigger the session to clear as that will make dev practically impossible.Billfold
B
9

I've finally found the solution.

To the provider options I added the following:

session: {
        jwt: true,
        maxAge: 30 * 24 * 60 * 60

    }

The session callback I changed to be the following:

async session(session, token) {
    //session.accessToken = token.accessToken;
    console.log("Session token");
    console.log(token);
    if (userAccount !== null)
    {
        session.user = userAccount;
    }
    else if (typeof token !== typeof undefined)
    {
        session.token = token;
    }
    console.log("session callback returning");
    console.log(session);
    return session;
}

The jwt callback was as follows:

async jwt(token, user, account, profile, isNewUser) {
    console.log("JWT Token User");
    console.log(token.user);
    if (typeof user !== typeof undefined)
    {
         token.user = user;
    }
    return token;
}

Basically I was misunderstanding that I needed to use the jwt callback and on the first call to this callback, the user model that is set from the signIn callback is used so I can add that to the token which can then be added to the session in the session callback.

The issue in subsequent requests to jwt, the user parameter doesn't get set so I was then setting the token user object to be undefined which is why my session was getting blanked.

I don't understand though why I didn't seem to get the behaviour when running it as a production build.

Billfold answered 24/4, 2021 at 21:36 Comment(1)
For anyone else still having issue NextAuth and credentials provider I've written a blog post detailing how to register a user and login as that user. Can find it on my blog at blog.devso.io/…Billfold
B
0

I had an issue with the session callback that never triggered and the useSession is undefined and the status is unauthenticated:

I'm Using a custom provider oryhydra the solution I found is:

  • Add the userinfo endpoint to provider configuration in ...nextauth.ts
  • Add the profile function in provide configuration and you could update the session and jwt in the callbacks function.

if this not help you could enable a debug mode by adding the prop debug: true in provider configuration.

Boisterous answered 26/6, 2022 at 16:43 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.