ndom91 / next-auth-example-sign-in-page

Example Auth.js v5 Custom Signin Page
https://authjs-custom-page-example.vercel.app
MIT License
89 stars 32 forks source link

missing csrf #5

Closed M-Pedrami closed 7 months ago

M-Pedrami commented 7 months ago

Hello, First off thank you for the example. I cloned your repo and installed all the dependencies. The login page itself is loading however when I click on sign in with GitHub or with credentials nothing happens. The terminal logs out this error: missing csrf: csrf token was missing during an action sign in.

I appreciate any help.

ndom91 commented 7 months ago

@M-Pedrami thanks for checking it out! I noticed this too and am working on it myself as we speak :pray:

I'll update you here once I find a solution

smultar commented 7 months ago

Let me know if your solution also works for the bun runtime.

ndom91 commented 7 months ago

@M-Pedrami this issue seems to be solved for this project in prod. However, I think there might be an issue with Next.js still.

I'm using next@14.3.0-canary in main right now, and it works in prod, but it still has this error in local dev.

What seems to be happening is the following, when I first click the "Sign in" button, it takes me to /auth/signin (the default signin page route) but renders the app/auth/login/page.tsx content (from my custom signin page), as it should. So its showing the wrong route initially.

Then, when entering the correct PW and clicking "signin with credentials" there, it responds with the "MissingCSRF" redirect error.

However, if I manually reload the page while its showing /auth/signin, it'll fix and show the /auth/login route correctly in the URL bar then. And thennn the signin works without throwing the "MissingCSRF" error.

I went back through more next.js releases, and the first one to exhibit this weird path behaviour was 14.2.0-canary.48. So anything before that, like -canary.48 or the latest 14.1.3 works both locally and in prod as expected.

lob0codes commented 6 months ago

Hi ndom91,

I saw the same behavior that you describe with your project and with my own project.

Just a minor thing that I saw is that initially when pressing the Sign In button, the route that is executed is "/api/auth/signin" instead of "/auth/signin", nothing that adds to the discussion xD.

I wonder if somehow this unexpected route and render management just makes a desynchronization of what CSFR token the server expects and what the OAuth provider is giving back.

I will try rolling back to the versions suggested to avoid this issue, thanks for the info!.

lob0codes commented 6 months ago

It works!, rolling back to 14.1.3 makes it work.

andresgutgon commented 6 months ago

This is still not working for me in NextJS 14.2.3

Instead of using default signIn method that does the redirect to /api/auth/ original endpoint. I do my own link

// server action to get from `next/headers` the origin
import { SIGNIN_URL } from '$/auth'
import { headers } from 'next/headers'
import { redirect } from 'next/navigation'

export async function signinLink({ redirectTo }: { redirectTo?: string }) {
  const origin = headers().get('origin')
  const callbackUrl = redirectTo ? redirectTo : origin ?? '/'
  redirect(`${SIGNIN_URL}?callbackUrl=${callbackUrl}`)
}

And then I have a custom component that use that header

import { signinLink } from '$/auth'

function SigninLink({ redirectTo }: { redirectTo?: string }) {
  return (
    <form
      action={async (formData) => {
        'use server'
        const redirectTo = formData.get('redirectTo')?.toString()
        await signinLink({ redirectTo })
      }}
    >
      <input type='hidden' name='redirectTo' value={redirectTo} />
      <button type='submit'>Sign In</button>
    </form>
  )
}

Lastly I use that component. Note that use can pass a custom redirectTo

// My home apps/page.tsx
export default function Home() {
  return <SigninLink redirectTo='/some/page' />
}
bdermody commented 5 months ago

@andresgutgon Hola Andres!! Did you get this working? I am still stuck on this :(

andresgutgon commented 5 months ago

Hola : ) Maybe try newer version of nextjs 15.0.0-canary.25. It's working for me now the same code

ndom91 commented 5 months ago

Yeah afaik this is a Next issue with 14.2.1+. Haven't had time to dig in further unfortunately.

There's a thread on the main next-auth repo as well here: https://github.com/nextauthjs/next-auth/issues/10585

august25 commented 4 months ago

The issue here is in env.ts it's setting the default config.basePath to "/api/auth" and so rather than attempting to load /auth/signin it's attempting to load /api/auth/signin, a refresh will cause it to load the proper path.

It's attempting to set the url to either of these: process.env.AUTH_URL ?? process.env.NEXTAUTH_URL

If not found it reverts to /api/auth

This is the code that creates the url, you can see it uses config.basePath, which defaults to /api/auth

const signInURL = createActionURL("signin", // @ts-expect-errorx-forwarded-protois not nullable, next.js sets it by default headers.get("x-forwarded-proto"), headers, process.env, config.basePath);

So for now to bypass I just direct users to "/auth/signin" directly as mentioned above. Setting one of the env vars above will break google. Still new to next-auth so maybe there's additional configurations needed to get it to work, but easiest solution is just to direct the user to "/auth/signin" yourself.

RushilJalal commented 4 months ago

Guys I received the same error [auth][error] MissingCSRF: CSRF token was missing during an action signin. Read more at https://errors.authjs.dev#missingcsrf at t6 (/var/task/.next/server/chunks/277.js:17:66137) at iN (/var/task/.next/server/chunks/277.js:393:46096) at async iz (/var/task/.next/server/chunks/277.js:393:50436) at async /var/task/node_modules/next/dist/compiled/next-server/app-route.runtime.prod.js:6:34666 at async eS.execute (/var/task/node_modules/next/dist/compiled/next-server/app-route.runtime.prod.js:6:25813) at async eS.handle (/var/task/node_modules/next/dist/compiled/next-server/app-route.runtime.prod.js:6:35920) at async es (/var/task/node_modules/next/dist/compiled/next-server/server.runtime.prod.js:16:25461) at async ei.responseCache.get.routeKind (/var/task/node_modules/next/dist/compiled/next-server/server.runtime.prod.js:17:1026) at async r3.renderToResponseWithComponentsImpl (/var/task/node_modules/next/dist/compiled/next-server/server.runtime.prod.js:17:508) at async r3.renderPageComponent (/var/task/node_modules/next/dist/compiled/next-server/server.runtime.prod.js:17:5028)

how can I fix this? My repo: https://github.com/RushilJalal/rushil-ecommerce-store

andresgutgon commented 4 months ago

Try this @RushilJalal https://stackoverflow.com/a/78577718

Assuming this is a server component you do this:

import { cookies } from "next/headers";

export default async function UserButton() {

  const session = await auth();
  if (!session?.user) {
    const csrfToken = cookies().get("authjs.csrf-token")?.value ?? ""
    return <SignIn csrfToken={csrfToken} />;
  }
  //...
}

Then you change this component:

// ./components/auth-components.tsx
export function SignIn({
  provider,
  csrfToken
}: { provider?: string; csrfToken: string } & React.ComponentPropsWithRef<typeof Button>) {
  return (
    <form
      action={async () => {
        "use server";
        await signIn(provider);
      }}
    >
      <!-- HIDDEN input with the csrfToken --> 
      <input type="hidden" name="csrfToken" value={csrfToken} />
      <Button {...props}>Sign In</Button>
    </form>
  );
}
comptyadel96 commented 2 months ago

if you have tried everything and still struggle , this is the code that worked for me ( i'am using next auth v5 beta and next V14 ) this is my custom signIn page :

"use client"
import { getProviders, signIn, getCsrfToken } from "next-auth/react"
import { useEffect, useState } from "react"
import clsx from "clsx"

export default function Page() {
  const [providers, setProviders] = useState({})
  const [csrfToken, setCsrfToken] = useState("")

  useEffect(() => {
    // get the csrf token from the provider
    async function loadProviders() {
      const authProviders = await getProviders()
      setProviders(authProviders)

      const csrf = await getCsrfToken()
      setCsrfToken(csrf)
    }
    loadProviders()
  }, [])

  return (
    <div className="flex flex-col lg:gap-4">
      {/* Email / password login form */}
      <form
        className="flex flex-col bg-amber-50 p-16 rounded-lg gap-4 self-center"
        method="post"
        action="/api/auth/callback/credentials"
      >
        <input type="hidden" name="csrfToken" value={csrfToken} />
        <div className="flex flex-col lg:max-w-[75%]">
          <div className="flex flex-col">
            <label htmlFor="email">
              Email
              <input className="border shadow-md" name="email" id="email" />
            </label>
          </div>
          <div className="flex flex-col">
            <label htmlFor="password">
              Password
              <input
                className="border shadow-md"
                name="password"
                id="password"
                type="password"
              />
            </label>
          </div>
        </div>
        <input
          type="submit"
          value="Login"
          className="bg-red-500 cursor-pointer px-3 py-1 rounded-lg font-semibold text-white"
        />
      </form>

      {/*  (Google, etc.) form */}
      {providers &&
        Object.values(providers).map((provider) => (
          <div key={provider.name} className="self-center">
            <button
              onClick={() => signIn(provider.id, { callbackUrl: "/profil" })}
              className={clsx("px-3 py-1 shadow-md", {
                "bg-green-300": provider.name == "Google",
              })}
            >
              <span>Connectez-vous avec {provider.name}</span>
            </button>
          </div>
        ))}
    </div>
  )
}

and this is my auth.ts file :

import NextAuth from "next-auth"
import Google from "next-auth/providers/google"
import Credentials from "next-auth/providers/credentials"
import type { Provider } from "next-auth/providers"
import { PrismaClient } from "@prisma/client"

const prisma = new PrismaClient()

const providers: Provider[] = [
  // Credentials({
  //   credentials: { password: { label: "Password", type: "password" } },
  //   authorize(c) {
  //     if (c.password !== "password") return null
  //     return {
  //       id: "test",
  //       name: "Test User",
  //       email: "test@example.com",
  //     }
  //   },
  // }),
  Google({
    clientId: process.env.AUTH_GOOGLE_ID,
    clientSecret: process.env.AUTH_GOOGLE_SECRET,
  }),

]

export const providerMap = providers
  .map((provider) => {
    if (typeof provider === "function") {
      const providerData = provider()
      return { id: providerData.id, name: providerData.name }
    } else {
      return { id: provider.id, name: provider.name }
    }
  })
  .filter((provider) => provider.id !== "credentials")

export const { handlers, auth, signIn, signOut } = NextAuth({
  // debug: true,
  providers,
  callbacks: {
    async signIn({ user, account, profile, credentials }) {
      console.log("Profil Google:", profile)

      const existingUser = await prisma.user.findUnique({
        where: { email: user.email },
      })

      if (!existingUser) {
        console.log("Créer un nouvel utilisateur.")
        await prisma.user.create({
          data: {
            name: user.name,
            email: user.email,
            image: user.image.replace("=s96-c", "=s400-c"),
          },
        })
      }

      return true
    },
  },

  pages: {
    signIn: "/signin",
    signOut: "/signout",
  },
})

if you have any question please let me know ;)

uhrohraggy commented 2 months ago

Originally I thought this worked, but I am deploying to Google (Firebase App Hosting), which fails even for the default (non-custom) sign in page

next-auth-csrf-error

It of course works locally but fails in prod. I'm going to add to an existing next-auth issue, but reporting here as search brings people here.

comptyadel96 commented 2 months ago

Originally I thought this worked, but I am deploying to Google (Firebase App Hosting), which fails even for the default (non-custom) sign in page next-auth-csrf-error

It of course works locally but fails in prod. I'm going to add to an existing next-auth issue, but reporting here as search brings people here.

did you configured .env file ? in the hosting provider ? also verify that sessions are working and that this error is not related where you deployed your app .