1

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

1 Answer 1

0

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.

Sign up to request clarification or add additional context in comments.

1 Comment

This does not provide an answer to the question. Once you have sufficient reputation you will be able to comment on any post; instead, provide answers that don't require clarification from the asker. - From Review

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.