Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Server Side providers/csrfToken/getCsrfToken is null on page reload on dev server #646

Closed
2 of 5 tasks
yournatalita opened this issue Sep 7, 2020 · 13 comments
Closed
2 of 5 tasks
Labels
question Ask how to do something or how something works stale Did not receive any activity for 60 days

Comments

@yournatalita
Copy link

yournatalita commented Sep 7, 2020

Your question
version: "^3.1.0"
mode: production

I use the credentials-signin as described here.

I get csrfToken on server-side as described in documentation

SignIn.getInitialProps = async (context) => {
  return {
    csrfToken: await csrfToken(context)
  }
}

I redirect client-side as described in one of your issue treads

useEffect(() => {
    if (!loading && !session) {
      Router.push(REDIRECT_URL);
    }
  }, [session, loading]);

The problem is
When app redirects from another page via Router.push i get the correct token on signin page.

When user reloads signin page manually, for example via F5, provider, csrfToken and getCsrfToken are null (cookies are fine) and console logs https://next-auth.js.org/errors#client_fetch_error error with javascript error SyntaxError: Unexpected token in JSON at position, and by "<" in it I can assume REST API returns a DOC page and breakes somehow.

Is this a bug or I am missing something?

When the person tries login with credentials without csrfToken it redirects to /api/auth/signin?csrf=true and not in my custom error page.

Is this a bug or designed behaviour? I don't need build-in auth page at all and I find it weird since it still creates an empty page https://prnt.sc/ucur6y which means for Next the URL is not breaking the 404 case.

Additional Info that might be related:

  1. I have client-side redirect to custom signin page (changed via pages)
pages: {
    signin: '/auth/signin',
    error: '/auth/signin', // Error code passed in query string as ?error=
}
  1. This is might be a bug related to TypeError: Cannot read property 'csrfToken' of null #299 but it is closed during v3 upgrade.
  2. I'm 100% sure NEXTAUTH_URL is in the right place and if my site is site.com it doesn't work with both
    NEXTAUTH_URL=http://site.com and NEXTAUTH_URL=http://site.com/api/auth
  3. My dev server is working on http and served via container in kubernetes
  4. I've tried both csrfToken and getCsrfToken
  5. providers doesn't work in this case too
  6. production mode in localhost doesn't show that issue so it makes harder to debug

What are you trying to do
I am trying to make a custom credentials signin page.

Things I'm trying to achieve:

  1. csrfToken is not null when user reloads page
  2. login without csrfToken should return the error like the .reject() in authorize callback
  3. Ability to specify custom signin page without creating empty default page to avoid custom 404 config

Currently I use csrfToken client-side as workaround.

I would be happy with any ideas for changes in my settings or explanation of this behavoiur.

Feedback
Documentation refers to searching through online documentation, code comments and issue history. The example project refers to next-auth-example.

  • Found the documentation helpful
  • Found documentation but was incomplete
  • Could not find relevant documentation
  • Found the example project helpful
  • Did not find the example project helpful
@yournatalita yournatalita added the question Ask how to do something or how something works label Sep 7, 2020
@znk
Copy link

znk commented Sep 7, 2020

Working on a white-label, product, I get the same error after copying the internal pages

@iaincollins
Copy link
Member

I appreciate I don't think this fully resolves your query and will follow up, but I think it should resolve most of it:

In 3.0 we made all the options camelCase to be consistent, so if you change 'signin' to 'signIn' here, that should resolve the problem with it not redirecting to your sign in page:

pages: {
    signIn: '/auth/signin',
    error: '/auth/signin', // Error code passed in query string as ?error=
}

(I appreciate this was an annoying change and could have been handled better, but is a one time growing pain for the lib.)

The route /api/auth/csrf should always work - and so should the client method.

I can't see any issues with that. I wonder if it's sometimes failing on dev due to the page being rebuilt by Next.js?

Dev mode is a bit weird in that respect and can trigger multiple page calls (and some failures) and is a bit out of our control.

@znk Did you also upgrade from a previous version or did you follow a tutorial or example from somewhere? I ask as a few folks have reported running into the same issue and am trying to work out if we have outdated docs / examples somewhere or if it's just confusing and we need to improve the error / warnings here.

@luigimassa
Copy link

Hi,
I resolved the problem
I use the latest nextjs
getInitialProps is deprecate. I used getServerSideProps

export async function getServerSideProps(context) {
  const csrfToken = await csrfTokenF(context);
  return { props: { csrfToken } };
}

You can see the nextjs doc in https://nextjs.org/docs/basic-features/data-fetching#getserversideprops-server-side-rendering

@yournatalita
Copy link
Author

yournatalita commented Oct 1, 2020

As I can see getInitialProps is not deprecated in last release. Neither code docs neither documentation says about it.

They just run another purpose by design and authors recommend to use getServerSide-way for data fetching for more optimized result.

But still getInitialProps is good for SSR + SPA as it merges client store with server store while routing as it works both from client side or from initial routing.

You can't just change the function as it will require architecture change of big part of app, and since the getInitialProps is not deprecated in documentation, I don't think that resolves the whole problem. But it will help for someone in their cases for sure.

@stale
Copy link

stale bot commented Dec 5, 2020

Hi there! It looks like this issue hasn't had any activity for a while. It will be closed if no further activity occurs. If you think your issue is still relevant, feel free to comment on it to keep ot open. Thanks!

@stale stale bot added the stale Did not receive any activity for 60 days label Dec 5, 2020
@stale
Copy link

stale bot commented Dec 12, 2020

Hi there! It looks like this issue hasn't had any activity for a while. To keep things tidy, I am going to close this issue for now. If you think your issue is still relevant, just leave a comment and I will reopen it. (Read more at #912) Thanks!

@stale stale bot closed this as completed Dec 12, 2020
@smahato-nap
Copy link

@yournatalita I am facing this issue in production I'm getting www.example.com/api/auth/signin?csrf=true and doesn't signin to the page, though it was working fine in local host was signing in. I dont know whats wrong please help me. I am new to this.

@muftisamiullah
Copy link

i m using SignIn.getInitialProps = async (context) => { return { csrfToken: await csrfToken(context), providers: await providers() } }

but when i reload the suth/sign page it shows me null in providers causing an exception

@muftisamiullah
Copy link

@yournatalita I am facing this issue in production I'm getting www.example.com/api/auth/signin?csrf=true and doesn't signin to the page, though it was working fine in local host was signing in. I dont know whats wrong please help me. I am new to this.

are using getInitialProps to get providers and others?

@gyull0210
Copy link

I am also experiencing the same error and cannot get the csrfToken while rendering to the server side.
The strange thing is that social login worked well at first.
The OAuth2 client ID and client secret are also properly filled in.

import { useState } from 'react'
import Head from 'next/head'
import Image from 'next/image'
import Link from 'next/link'
import { FaTwitter } from 'react-icons/fa'
import { FcGoogle } from 'react-icons/fc'
import { signIn, signOut, useSession, getSession, getProviders, getCsrfToken } from 'next-auth/react'
import { redirect } from 'next/dist/server/api-utils'
import { useRouter } from 'next/router'

export default function Login({getProviders}) {
  const router = useRouter();
  const { data: session, status } = useSession();

  const handleGoogle = async (e)=> {
    e.preventDefault();
    const response = await signIn("google");
  }

  const handleTwitter = async (e) => {
    e.preventDefault();
    const response = await signIn("twitter");
  }

  const [isChecked, setIsChecked] = useState(false);

  const handleOnSubmit = async (e) => {
    e.preventDefault();

    let email = e.target.email.value;
    let password = e.target.password.value;

    
    const response = await signIn("email-password-credential", {
      email,
      password,
      redirect: false,
      callbackUrl: "http://localhost:3000"
    });

    await router.push("/"); 

    console.log("email: "+email+", "+"password: "+password);
 
  }

  return (
    <div className="">
      <Head>
        <title>로그인</title>
        <meta name="description" content="example" />
        <link rel="icon" href="/favicon.ico" />
      </Head>

      <main>
        <div className="container mx-auto max-w-screen-md lg:my-25 p-4">
          <div className="flex flex-col items-center mb-6">
            
            <a className="hover:no-underline mb-2">
              <div className="flex items-center justify-center">
                <span className="font-semibold text-3xl tracking-tight">C</span>
                <span className="font-semibold text-3xl tracking-tight text-[#E6CEA0]">OO</span>
                <span className="font-semibold text-3xl tracking-tight">KIVEL</span>
              </div>
            </a>

            <div className="text-2xl font-semibold text-center">
              로그인
            </div>
          </div>

          <div className="">
            <div className="mb-4">
              <button type="button" className="w-full px-6 py-2 bg-[#FFFFFF] rounded border shadow hover:bg-gray-50 active:bg-gray-100 mb-2"
              onClick={handleGoogle}>
                <div className="flex items-center justify-center">
                  <span className="text-2xl mr-2"><FcGoogle/></span>
                  구글 계정으로 로그인
                </div>               
              </button>
              <button type="button" className="w-full px-6 py-2 bg-sky-400 rounded text-white border shadow hover:bg-sky-500 active:bg-sky-600"
              onClick={handleTwitter}>
                <div className="flex items-center justify-center">
                  <span className="text-xl mr-2"><FaTwitter/></span>
                  트위터 계정으로 로그인
                </div>
              </button> 
            </div>
          </div>

          <div className="text-center">
          또는
          </div>

          <form className="mx-auto" method="post" onSubmit={handleOnSubmit}>
            <div className="mb-4">
              <label className="block font-semibold" htmlFor="email">이메일</label>
              <input 
                type="text"
                id="email" 
                className="px-4 py-2 block w-full 
                border border-gray-400 rounded-md outline-none 
                text-base focus:outline-none focus:ring-1 focus:ring-gray-700 focus:border-gray-700"             
                placeholder="이메일을 입력해주세요" 
              />
              {isChecked && 
              <div className="">
                <span className="block text-red-600">이메일 또는 비밀번호가 잘못 입력되었습니다</span>
                <span className="block text-red-600">가입되어 있지 않은 이메일을 입력하였습니다</span>
              </div>}                    
            </div>
            <div className="mb-4">
              <label className="block font-semibold" htmlFor="password">비밀번호</label>
              <input
                type="password"
                id="password" 
                className="px-4 py-2 block w-full 
                border border-gray-400 rounded-md outline-none 
                text-base focus:outline-none focus:ring-1 focus:ring-gray-700 focus:border-gray-700" 
                placeholder="비밀번호를 입력해주세요" 
              />
              {isChecked && 
              <div className="">
                <span className="block text-red-600">이메일 또는 비밀번호가 잘못 입력되었습니다</span>
              </div>}                     
            </div>
        
            <div className="flex items-center mb-4">
              <input 
                type="checkbox"
                id="rememberMe" 
                className="w-4 h-4 text-gray-700 mr-2 rounded-lg border-2 border-gray-300 after:bg-gray-800 focus:ring-0"/>
              <label className="text-base mr-2" htmlFor="rememberMe">로그인 유지</label>  
            </div>

            <div className="mb-4">
              <button 
                type="submit"
                className="w-full px-6 py-2 bg-stone-500 rounded-md border border-stone-500 text-white shadow hover:bg-stone-600 active:bg-stone-700">
                로그인
              </button>
            </div>        
            <div className="flex justify-center divide-x divide-gray-300">
              <Link href="/auth/signup">
                <a className="pr-2">회원가입</a>
              </Link>
              <Link href="/auth/member/passwordSearch">
                <a className="pl-2">비밀번호 찾기</a>
              </Link>
            </div>
          </form>
        </div>
      </main>
    </div>
  )
}

export async function getServerSideProps(context) {
  const { req } = context;
  const session = await getSession({ req });
  if (session) {
    return {
      redirect: { destination: "/" },
    };
  }

  return {
    props: {
      providers: await getProviders(context),
      csrfToken: await getCsrfToken(context),
    },
  };
}
import NextAuth from "next-auth"
import GoogleProvider from "next-auth/providers/google"
import TwitterProvider from "next-auth/providers/twitter"
import CredentialsProvider from "next-auth/providers/credentials"

export default NextAuth({
  providers: [
    GoogleProvider({
      clientId: process.env.GOOGLE_ID,
      clientSecret: process.env.GOOGLE_SECRET,
      authorization: {
        params: {
          prompt: "consent",
          access_type: "offline",
          response_type: "code"
        }
      }
    }),
    TwitterProvider({
      clientId: process.env.TWITTER_ID,
      clientSecret: process.env.TWITTER_SECRET,
      version: "2.0",
    }),
],
pages:{
    signIn: '/auth/login',
    error: '/auth/error',
    newUser: '/auth/signup',
  },
  callbacks: {
    async jwt({ token, account }) {
      // Persist the OAuth access_token to the token right after signin
      if (account) {
        token.accessToken = account.access_token
      }
      return token
    },
    async session({ session, token, user }) {
      // Send properties to the client, like an access_token from a provider.
      session.accessToken = token.accessToken
      return session
    }
  }
})
NEXTAUTH_URL="https://localhost:3000"

GOOGLE_ID="GOOGLE_ID"
GOOGLE_SECRET="GOOGLE_SECRET"

TWITTER_ID="TWITTER_ID"
TWITTER_SECRET="TWITTER_SECRET"

It was run in development mode in the local environment, and no other settings were made. It is not implemented using DB, it only checks authentication on social media to get emails, nicknames, and profile images.

[next-auth][error][CLIENT_FETCH_ERROR] 
https://next-auth.js.org/errors#client_fetch_error request to https://localhost:3000/api/auth/providers failed, reason: write EPROTO 8208:error:1408F10B:SSL routines:ssl3_get_record:wrong version number:c:\ws\deps\openssl\openssl\ssl\record\ssl3_record.c:332:
 {
  error: {
    message: 'request to https://localhost:3000/api/auth/providers failed, reason: write EPROTO 8208:error:1408F10B:SSL routines:ssl3_get_record:wrong version number:c:\\ws\\deps\\openssl\\openssl\\ssl\\record\\ssl3_record.c:332:\n',
    stack: 'FetchError: request to https://localhost:3000/api/auth/providers failed, reason: write EPROTO 8208:error:1408F10B:SSL routines:ssl3_get_record:wrong version number:c:\\ws\\deps\\openssl\\openssl\\ssl\\record\\ssl3_record.c:332:\n' +
      '\n' +
      '    at ClientRequest.<anonymous> (D:\\toy\\nextjs+tailwindcss+storybook\\nextjs-tailwindcss-storybook\\node_modules\\next\\dist\\compiled\\node-fetch\\index.js:1:65756)\n' +
      '    at ClientRequest.emit (node:events:513:28)\n' +
      '    at TLSSocket.socketErrorListener (node:_http_client:481:9)\n' +
      '    at TLSSocket.emit (node:events:513:28)\n' +
      '    at emitErrorNT (node:internal/streams/destroy:157:8)\n' +
      '    at emitErrorCloseNT (node:internal/streams/destroy:122:3)\n' +
      '    at processTicksAndRejections (node:internal/process/task_queues:83:21)',
    name: 'FetchError'
  },
  url: 'https://localhost:3000/api/auth/providers',
  message: 'request to https://localhost:3000/api/auth/providers failed, reason: write EPROTO 8208:error:1408F10B:SSL routines:ssl3_get_record:wrong version number:c:\\ws\\deps\\openssl\\openssl\\ssl\\record\\ssl3_record.c:332:\n'
}

error - SerializableError: Error serializing `.csrfToken` returned from `getServerSideProps` in "/auth/login".
Reason: `undefined` cannot be serialized as JSON. Please use `null` or omit this value.
    at isSerializable (D:\toy\nextjs+tailwindcss+storybook\nextjs-tailwindcss-storybook\node_modules\next\dist\lib\is-serializable-props.js:36:19)
    at D:\toy\nextjs+tailwindcss+storybook\nextjs-tailwindcss-storybook\node_modules\next\dist\lib\is-serializable-props.js:43:66
    at Array.every (<anonymous>)
    at isSerializable (D:\toy\nextjs+tailwindcss+storybook\nextjs-tailwindcss-storybook\node_modules\next\dist\lib\is-serializable-props.js:40:39)
    at Object.isSerializableProps (D:\toy\nextjs+tailwindcss+storybook\nextjs-tailwindcss-storybook\node_modules\next\dist\lib\is-serializable-props.js:63:12)
    at Object.renderToHTML (D:\toy\nextjs+tailwindcss+storybook\nextjs-tailwindcss-storybook\node_modules\next\dist\server\render.js:570:67)
    at runMicrotasks (<anonymous>)
    at processTicksAndRejections (node:internal/process/task_queues:96:5)
    at async doRender (D:\toy\nextjs+tailwindcss+storybook\nextjs-tailwindcss-storybook\node_modules\next\dist\server\base-server.js:669:38)
    at async cacheEntry.responseCache.get.isManualRevalidate.isManualRevalidate (D:\toy\nextjs+tailwindcss+storybook\nextjs-tailwindcss-storybook\node_modules\next\dist\server\base-server.js:778:28) {
  page: '/auth/login'
}

But it gave me this error.

@dbrown428
Copy link

@gyull0210 if you do a GET request on the CSRF route (/api/auth/csrf) in your web browser or Postman does it return a valid CSRF token?

I often run multiple NextJS instances and, being a bit absent minded, I often mix up the port mappings, eg. http://localhost:3001 vs http://localhost:3000 with what value is set in my .env file

When I do, I also get [CLIENT_FETCH_ERROR], but slightly different than yours, such as:

[next-auth][error][CLIENT_FETCH_ERROR] 
https://next-auth.js.org/errors#client_fetch_error invalid json response body at http://localhost:3000/api/auth/csrf reason: Unexpected token < in JSON at position 0 {
  error: {
    message: 'invalid json response body at http://localhost:3000/api/auth/csrf reason: Unexpected token < in JSON at position 0',
    stack: 'FetchError: invalid json response body at http://localhost:3000/api/auth/csrf reason: Unexpected token < in JSON at position 0\n' +
      '    at /web/node_modules/next/dist/compiled/node-fetch/index.js:1:51220\n' +
      '    at processTicksAndRejections (node:internal/process/task_queues:96:5)',
    name: 'FetchError'
  },
  url: 'http://localhost:3000/api/auth/csrf',
  message: 'invalid json response body at http://localhost:3000/api/auth/csrf reason: Unexpected token < in JSON at position 0'
}
error - SerializableError: Error serializing `.csrfToken` returned from `getServerSideProps` in "/login".
Reason: `undefined` cannot be serialized as JSON. Please use `null` or omit this value.
    at isSerializable (/web/node_modules/next/dist/lib/is-serializable-props.js:36:19)
    at /web/node_modules/next/dist/lib/is-serializable-props.js:43:66
    at Array.every (<anonymous>)
    at isSerializable (/web/node_modules/next/dist/lib/is-serializable-props.js:40:39)
    at Object.isSerializableProps (/web/node_modules/next/dist/lib/is-serializable-props.js:63:12)
    at Object.renderToHTML (/web/node_modules/next/dist/server/render.js:570:67)
    at processTicksAndRejections (node:internal/process/task_queues:96:5)
    at async doRender (/web/node_modules/next/dist/server/base-server.js:669:38)
    at async cacheEntry.responseCache.get.isManualRevalidate.isManualRevalidate (/web/node_modules/next/dist/server/base-server.js:778:28)
    at async /web/node_modules/next/dist/server/response-cache/index.js:80:36 {
  page: '/login'
}

Just wanted to share in hopes that it might inspire an idea for you. Good luck :)

@jeroenpelgrims
Copy link

jeroenpelgrims commented Mar 10, 2023

Just wanted to share in hopes that it might inspire an idea for you. Good luck :)

It pointed me in the right direction at least!
I got an error almost exactly like yours. (with the Reason: undefined cannot be serialized as JSON)
But instead of url: 'http://localhost:3000/api/auth/csrf',, I had url: 'https://localhost:3000/api/auth/csrf',. (https vs http)

After looking at next-auth's source code I saw that, if it's set, they use the env variable VERCEL_URL to generate the csrf token endpoint url.
I had set this variable in my .env file because I needed it for something else. I set the value to localhost:3000 because according to the vercel docs this should be set without the protocol in the url.

And I'm guessing next-auth automatically prefixes that url with https://.
My dev server is running on http of course, and thus the request to the csrf endpoint fails.

A solution is to either unset VERCEL_URL in the .env file, or to set it's value to include the protocol (http://localhost:3000).
Although the latter option can probably cause other issues since this value should not include the protocol...

@kiastorm
Copy link

I had this and the issue was that I was running the app on a different port to what was used in the NEXTAUTH_URL env variable.

i.e app was localhost:4321 and NEXTAUTH_URL was localhost:3001. Changed it to use port 4321 and it worked :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Ask how to do something or how something works stale Did not receive any activity for 60 days
Projects
None yet
Development

No branches or pull requests

10 participants