Toko Game
Toko Game

Reputation: 53

NextAuth: Session Callback Not Triggered on Vercel but Triggerred Fine on Localhost

On localhost, Google login works perfectly fine. But upon deploying this to Vercel, session is always empty {} after successful Google login. I looked at the logs on Vercel and I can see that session callback is never executed on Vercel (only executed on localhost)

I've also created issue here and vercel url to reproduce it: https://github.com/nextauthjs/next-auth/issues/9083

[...nextauth].js:

export const authOptions = {
  providers: [
    GoogleProvider({
      clientId: process.env.GOOGLE_CLIENT_ID,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET,
    }),
  ],
  secret: process.env.NEXTAUTH_SECRET,
  jwt: {
    secret: process.env.NEXTAUTH_SECRET,
    encryption: true,
  },
  debug: true,
  session: {
    // Choose how you want to save the user session.
    // The default is `"jwt"`, an encrypted JWT (JWE) stored in the session cookie.
    // If you use an `adapter` however, we default it to `"database"` instead.
    // You can still force a JWT session by explicitly defining `"jwt"`.
    // When using `"database"`, the session cookie will only contain a `sessionToken` value,
    // which is used to look up the session in the database.
    strategy: "jwt",

    // Seconds - How long until an idle session expires and is no longer valid.
    // 30 days.
    // We need to handle if backend returns error which says that token has expired
    // and then force logout user.
    maxAge: 30 * 24 * 60 * 60,

    // Seconds - Throttle how frequently to write to database to extend a session.
    // Use it to limit write operations. Set to 0 to always update the database.
    // Note: This option is ignored if using JSON Web Tokens
    // updateAge: 24 * 60 * 60, // 24 hours

    // The session token is usually either a random UUID or string, however if you
    // need a more customized session token string, you can define your own generate function.
    // generateSessionToken: () => {
    //   return randomUUID?.() ?? randomBytes(32).toString("hex")
    // }
  },

  callbacks: {
    async jwt({ token, user, account }) {
      // Persist the user.authToken from signIn callback to the token.authToken

      if (account) {
        // Handle the Google ID token here
        const googleIdToken = account.id_token;
        // console.log("nextauth.js jwt: got googleIdToken " + googleIdToken);
        // Send the Google ID token to our backend for verification
        // and exchange it for our backend's token
        // TODO: no way to get locale for now.
        const res = await reqGoogleTokenSignIn(null, googleIdToken);
        const member = res?.data?.data;
        if (member == null) {
          console.log(
            "nextauth.js jwt reqGoogleTokenSignIn: Failed response from be"
          );
          return false;
        }

        console.log(
          "nextauth.js jwt reqGoogleTokenSignIn: Received response from be. authToken: " +
            member.authToken
        );

        let newToken = {};
        newToken.userId = member.id;
        newToken.authToken = member.authToken;
        newToken.profilePictureUrl = member.profilePictureUrl;
        newToken.fullName = member.fullName;
        newToken.emailAddress = member.emailAddress;
        newToken.phoneNumber = member.phoneNumber;

        console.log("nextauth.js jwt: setting token's userId and authToken");
        return newToken;
      }

      return token;
    },

    async session({ session, token }) {
      // Persist the token.authToken from jwt callback to the session.authToken

      console.log(
        "nextauth.js session: setting session's userId and authToken"
      );
      session.userId = token?.userId;
      session.authToken = token?.authToken;
      session.profilePictureUrl = token?.profilePictureUrl;
      session.fullName = token?.fullName;
      session.emailAddress = token?.emailAddress;
      session.phoneNumber = token?.phoneNumber;

      return session;
    },
  },
};
export default NextAuth(authOptions);

My component that uses session:

LoginBtn.js

function LoginBtn({ provider }) {
  const { data: session, status } = useSession();
  const [loggedIn, setLoggedIn] = useState(null);

  let loginButton = (
    <ImageContainer>
      <OptimizedImage
        onClick={() => signIn("google", { callbackUrl: "/members/profile" })}
        priority={true}
        src="/assets/google-login.png"
        alt={"Login with Google"}
        height={32}
        width={82}
      />
    </ImageContainer>
  );

  let profileButton = (
    <ImageContainer>
      <Link prefetch={false} href={"/members/profile"} legacyBehavior>
        <OptimizedImage
          priority={true}
          src={session?.profilePictureUrl}
          alt={session?.emailAddress + " profile image"}
          height={25}
          width={25}
        />
      </Link>
    </ImageContainer>
  );

  useEffect(() => {
    if (session == null && status === "loading") {
      return;
    } else if (session == null && status === "unauthenticated") {
      setLoggedIn(false);
      return;
    } else if (session != null && status === "authenticated") {
      setLoggedIn(true);
      return;
    }
  }, [session, status]);

  return loggedIn == null ? null : loggedIn ? profileButton : loginButton;
}

export default LoginBtn;

From Vercel's log: i can see these 2 logs printed:

nextauth.js jwt reqGoogleTokenSignIn: Received response from be. authToken: Lcoury0Lfl5MRHfRVs6GY280H144=

nextauth.js jwt: setting token's userId and authToken

but it's missing the log from session callback and there was no error or anything. This log is printed if i run it on localhost.

Please tell me what can be different between localhost and Vercel deployment?

Tried adding NEXTAUTH_SECRET on vercel env var. ADDED NEXTAUTH_URL (still did not make a difference) and then removed it since documentation says they are not necessary

Enabled nextauth debug mode to see the logs above

Upvotes: 1

Views: 315

Answers (1)

Maor Azulay
Maor Azulay

Reputation: 1

Have you made sure to change the callback URL in your Google client to the new deployment's domain?

It's best to just create another client for every environment, to avoid having to change the URL manually every time.

Upvotes: 0

Related Questions