nextauthjs / next-auth

Authentication for the Web.
https://authjs.dev
ISC License
24.57k stars 3.46k forks source link

Google sign-in returns null session in production #3208

Closed wbcdev closed 2 years ago

wbcdev commented 2 years ago

Question 💬

Hi,

I'm running into much trouble in getting the Google provider sign-in from our example app to work in production.
I'm currently deploying the nextjs server to a gcloud function. When I attempt log-in the session returned is either empty or is not being handled correctly afterwards, it's never actually worked, as of now it just flips me back to the homepage while not signing in.

My production domain is hosted and everything seems alright on Goolge's side. Nothing else needs to be approved there.

At first there was this error in Google cloud functions logger: /api/auth/error?error=OAuthCallback

and concurrently my app was showing the same thing in the browser and not sending me back to the homepage, but was showing 'try logging in with a different provider'.

The logger in Google Cloud Platform would also give this:

textPayload: "https://next-auth.js.org/errors#state_error OAuthCallbackError: Invalid state returned from OAuth provider"

“code: 'ERR_HTTP_HEADERS_SENT'”

https://next-auth.js.org/errors#oauth_callback_error Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client”

I don't understand that headers part.

But interestingly, upon moving to an earlier version of my code it now just returns a sign-in requester back to the homepage of the app while still saying 'not signed-in' and showing session as 'null' when console logged from the header.js component, no more OAuthCallbackError, but I suspect the same cause of the behavior. Everything runs smoothly in dev environment on localhost and when building and starting there. This led me to believe that it is just a discrepancy between setting the environment variables in production vs. having them set in env.local.

However, I still believe they are setting correctly on the function in deployment as I've tried changing NEXTAUTH_URL on the function (from mysite.web.app to my custom domain name) and it will reflect a change in production, I've looked at the GOOGLE_ID and secret and they're being set properly (no extra spaces). I've tried putting NEXT_AUTH in front of my envs in production as well. I even tried setting them in nextjs.config.

The api callback settings on google cloud platform seem to be fine, after all, they are working perfectly on localhost. It's just that once I go to Google sign-in for the production domain no session is being returned, or at least my header component thinks so, maddening, but it should pick it up fine with useSession just at it does on localhost, right?

I don't know if I need to be setting something up with jwt and session callbacks, as you can see from my commented out code below, but those didn't make any difference either on localhost (they worked either way for my case of just signing in) or in production (received the same error with or without jwt callback code) and I am to believe that the example code should just pretty much work as-is in production, so I'm going with that.

Has anyone else had this problem? And is there a certain consideration I need to have for setting environment variables in production with firebase at least?

I thought maybe because there was a 'TrustedScript' console error on the Google sign-in that that was the problem. But I'm not setting a Content Security Policy in the app and I really don't think it should be blocking, as it doesn't block me when production building on localhost even when giving that 'TrustedScript' page error in the console. Whereas, on the other hand, if I try to set a csp for the app in total, and fail at it, it totally does just block me outright. And I really hope it's not the csp because that's an entirely different question, how on earth to set it?

Thank you so much for helping out with this. I have literally tried everything I can think of.

How to reproduce ☕️

To reproduce: build example app and deploy nextjs server to firebase google cloud function. Host the site with firebase and have index.html redirect to server.js through "main": "server.js" in package.json, thus your hosted url will run the server and the app. Edit function on cloud console to see env vars or deploy the env vars when deploying the function.

package.json

"dependencies": {
    "@apollo/client": "^3.4.16",
    "crypto": "^1.0.1",
    "dompurify": "^2.3.3",
    "firebase": "^9.1.3",
    "firebase-admin": "^9.12.0",
    "firebase-functions": "^3.15.7",
    "isomorphic-dompurify": "^0.16.0",
    "nanoid": "^3.1.30",
    "next": "^11.0.0",
    "next-auth": "latest",
    "react": "^17.0.2",
    "react-dom": "^17.0.2",
    "sqlite3": "^5.0.2"
  },

server.js

const admin = require("firebase-admin");
const functions = require("firebase-functions");
const next = require("next");
const config = require("./next.config");

admin.initializeApp();

const dev = process.env.NODE_ENV !== "production";
const app = next({
    dev,
    // the absolute directory from the package.json file that initialises this module
    // IE: the absolute path from the root of the Cloud Function
    conf: config,
});
const handle = app.getRequestHandler();

const server = functions.https.onRequest((request, response) => {
    // log the page.js file or resource being requested
    console.log("File: " + request.originalUrl);
    return app.prepare().then(() => handle(request, response));
});

exports.nextjs = { server };

firebase.json

{
    "emulators": {
        "functions": {
            "port": 5001
        },
        "firestore": {
            "port": 8080
        },
        "hosting": {
            "port": 5000
        },
        "ui": {
            "enabled": true
        }
    },
    "functions": {
        "source": ".",
        "ignore": [
            "firebase.json",
            "firebase-debug.log",
            "**/.*",
            "**/node_modules/**",
            "components/**",
            "utils/**",
            "pages/**",
            "public/**",
            "firestore.rules",
            "readme.md"
        ],
    "runtime": "nodejs16"
    },
    "hosting":
        {
            "site": "mysite",
            "public": "public/",
            "cleanUrls": true,
      "ignore": [
      "firebase.json",
      "**/.*",
      "**/node_modules/**"
    ],
            "rewrites": [
                {
                    "source": "**",
                    "function": "nextjs-server"
                }
            ]
        }
}

[...nextauth.js]

import NextAuth from "next-auth"
import Providers from "next-auth/providers"

export default NextAuth({
  providers: [
    Providers.Google({
      clientId: process.env.GOOGLE_ID,
      clientSecret: process.env.GOOGLE_SECRET,
      authorizationUrl: 'https://accounts.google.com/o/oauth2/v2/auth?prompt=consent&access_type=offline&response_type=code',
      state: false,
    }),
  ],

  secret: process.env.SECRET,

  // jwt: {
  //   //encryption: true,
  //   secret: 
  //   signingKey: 
  //   encryptionKey: 
  // },

  // callbacks: {
  //   async signIn(user, account, profile, email, credentials) { 
  //     console.log("User Is");
  //     console.log(user);
  //     return true, user, account 
  //   },

  //   async jwt(token, user, account, profile, isNewUser) {
  //     if (account?.accessToken) {
  //         token.accessToken = account.accessToken;
  //         token.user = user
  //     }
  //     return token;
  //   },

  //   async session(session, user) { 
  //     console.log("SESSION CALLED");
  //     session.accessToken = user.accessToken;
  //     session.expires = user.expires;
  //     return session 
  //   },
  // },

   session: {
      jwt: true,
      maxAge: 30 * 24 * 60 * 60, // 30 days
   },
  theme: 'light',

  debug: false,
})

header.js

import React from 'react'
import Link from 'next/link'
import { csrfToken, signIn, signOut, useSession, getSession } from 'next-auth/client'
import styles from './header.module.css'

export default function Header () {
  const [ session, loading ] = useSession()
  console.log('Session coming through!')
  console.log(session)

  return (
    <header>
      <noscript>
        <style>{`.nojs-show { opacity: 1; top: 0; }`}</style>
      </noscript>
      <div className={styles.signedInStatus}>
        <p className={`nojs-show ${(!session && loading) ? styles.loading : styles.loaded}`}>
          {!session && <>
            <span className={styles.notSignedInText}>You are not signed in</span>
            <a
                href={`/api/auth/signin`}
                className={styles.buttonPrimary}
                onClick={(e) => {
                  e.preventDefault()
                  signIn('google')
                }}
              >
                Sign in
              </a>
          </>}
          {session && <>
            {session.user.image && <span style={{backgroundImage: `url(${session.user.image})` }} className={styles.avatar}/>}
            <span className={styles.signedInText}>
              <small>Signed in as</small><br/>
              <strong>{session.user.email || session.user.name}</strong>
              </span>
            <a
                href={`/api/auth/signout`}
                className={styles.button}
                onClick={(e) => {
                  e.preventDefault()
                  signOut()
                }}
              >
                Sign out
              </a>
          </>}
        </p>
      </div>
      <nav>
        <ul className={styles.navItems}>
          <li className={styles.navItem}><Link href="/"><a>Home</a></Link></li>
          <li className={styles.navItem}><Link href="/client"><a>Client</a></Link></li>
          <li className={styles.navItem}><Link href="/server"><a>Server</a></Link></li>
          <li className={styles.navItem}><Link href="/protected"><a>Protected</a></Link></li>
          <li className={styles.navItem}><Link href="/api-example"><a>API</a></Link></li>
          <li className={styles.navItem}><Link href="/clients"><a>Clients</a></Link></li>
        </ul>
      </nav>
    </header>
  )
}

Also, I noticed from the network tab in production that the session request was a GET request

Headers: Request Method: GET Status Code: 200

Response: {}

where I thought I read somewhere that it must be a POST request, which I thought was being forced when calling signIn('google').

I really want to use NextAuth in my project, and I appreciate any help you may offer.

Contributing 🙌🏽

No, I am afraid I cannot help regarding this

balazsorban44 commented 2 years ago

Could be related to https://firebase.google.com/docs/hosting/manage-cache#using_cookies

When using Firebase Hosting together with Cloud Functions or Cloud Run, cookies are generally stripped from incoming requests. This is necessary to allow for efficient CDN cache behavior. Only the specially-named __session cookie is permitted to pass through to the execution of your app.