gladly-team / next-firebase-auth

Simple Firebase authentication for all Next.js rendering strategies
https://nfa-example-git-v1x-gladly-team.vercel.app/
MIT License
1.34k stars 291 forks source link

Infinite loop when logging in #601

Closed Bowis closed 1 year ago

Bowis commented 1 year ago

When filing a bug report, please confirm you've done the following:

Describe the bug 0

I have the following setup using Next-firebase-auth:

Login page:

import {
  AuthAction,
  withAuthUser,
  withAuthUserSSR,
  withAuthUserTokenSSR,
} from "next-firebase-auth";
import { FormProvider, useForm } from "react-hook-form";

import Link from "next/link";
import { MoonLoader } from "react-spinners";
import PageHead from "../components/PageHead";
import { auth } from "../config/firebase";
import { signInWithEmailAndPassword } from "firebase/auth";
import { useRouter } from "next/router";
import { useState } from "react";

interface LoginType {
  email: string;
  password: string;
}
const LoginPage = () => {
  const methods = useForm<LoginType>({ mode: "onBlur" });
  const [error, setError] = useState("");
  const [loading, setLoading] = useState(false);

  const {
    register,
    handleSubmit,
    formState: { errors },
  } = methods;

  const onSubmit = async (data: LoginType) => {
    try {
      setLoading(true);
      const { email, password } = data;
      await signInWithEmailAndPassword(auth, email, password);
      setLoading(false);
    } catch (e: any) {
      setError(e.message);
      setLoading(false);
    }
  };

  return (
    <div className="flex min-h-screen flex-col justify-center py-12 sm:px-6 lg:px-8 bg-gray-50">
      <PageHead title="Inloggen" />
      <div className="bg-white py-8 shadow rounded-lg px-10 mx-auto">
        <FormProvider {...methods}>
          <form
            className="w-80 mx-auto pb-12 px-4"
            onSubmit={handleSubmit(onSubmit)}
          >
            <div className="mt-5">
              <label
                htmlFor="email"
                className="block text-sm font-medium text-gray-600"
              >
                Email
              </label>
              <input
                type="email"
                {...register("email", { required: "Email is verplicht" })}
                className="block mt-1 w-full appearance-none rounded-md border border-gray-300 px-3 py-2 shadow-sm focus:border-hurks-green focus:ring-hurks-green focus:outline-none  sm:text-sm"
              />
              {errors.email && (
                <p className="text-red-400 mt-1  text-sm font-medium">
                  {errors.email.message}
                </p>
              )}
            </div>
            <div className="mt-7">
              <label
                htmlFor="password"
                className="block text-sm font-medium text-gray-600"
              >
                Wachtwoord
              </label>
              <input
                type="password"
                {...register("password", {
                  required: "Wachtwoord is verplicht",
                })}
                className="block w-full mt-1 appearance-none rounded-md border border-gray-300 px-3 py-2shadow-sm focus:border-hurks-green  focus:ring-hurks-green  focus:outline-none  sm:text-sm"
              />
              {errors.password && (
                <p className="text-red-400 mt-1  text-sm font-medium">
                  {errors.password.message}
                </p>
              )}
            </div>
            <div className="text-sm mt-2">
              <Link
                href="/forgot-password"
                className="font-medium text-hurks-green "
              >
                Wachtwoord vergeten?
              </Link>
            </div>
            <div className="pt-8">
              <button
                type="submit"
                className="flex w-full justify-center rounded-md border border-transparent bg-hurks-green py-2 px-4 text-sm font-medium text-white shadow-sm hover:bg-hurks-green focus:outline-none   "
              >
                {loading && (
                  <MoonLoader
                    size={15}
                    color={"white"}
                    className="-ml-0.5 mr-2 "
                  />
                )}
                Login
              </button>
              {error && (
                <p className="text-red-400 mt-1  text-sm font-medium">
                  {error}
                </p>
              )}
            </div>
          </form>
        </FormProvider>
      </div>
    </div>
  );
};

export const getServerSideProps = withAuthUserTokenSSR({
  whenAuthed: AuthAction.REDIRECT_TO_APP,
})();

export default withAuthUser({
  whenAuthed: AuthAction.REDIRECT_TO_APP,
})(LoginPage);

Protected page:

import { AuthAction, withAuthUser, withAuthUserTokenSSR } from "next-firebase-auth";
import { deleteDoc, doc } from "firebase/firestore";
import { useContext, useState } from "react";

import DeleteModal from "../../components/DeleteModal";
import { GeneralContext } from "../../context/GeneralContext";
import Link from "next/link";
import MaterialsTable from "../../components/material/MaterialsTable";
import PageHead from "../../components/PageHead";
import { db } from "../../config/firebase";

const Materials = () => {
  const { showDeleteModal, setShowDeleteModal } = useContext(GeneralContext);
  const [materialId, setMaterialId] = useState<null | string>(null);

  const handleSetMaterialId = (id: string) => {
    setMaterialId(id);
  };

  const deleteMaterial = async () => {
    setShowDeleteModal(!showDeleteModal);
    await deleteDoc(doc(db, "materials", `${materialId}`));
  };
  return (
    <>
      <PageHead title="Materialen Overzicht" />
      <div className="mt-14 w-9/12 mx-auto">
        <div className="flex pb-8 justify-between items-center">
          <h1 className="text-2xl font-semibold text-gray-900">Materialen</h1>
          <Link
            href="/materials/create"
            type="button"
            className="items-center justify-center rounded-md bg-hurks-green px-4 py-2.5 text-sm font-medium text-white shadow-sm hover:bg-hurks-green sm:w-auto"
          >
            Voeg materiaal toe
          </Link>
        </div>
        <MaterialsTable handleSetMaterialId={handleSetMaterialId} />
      </div>
      <DeleteModal deleteItem={deleteMaterial} />
    </>
  );
};

export const getServerSideProps = withAuthUserTokenSSR({
  whenUnauthed: AuthAction.REDIRECT_TO_LOGIN,
})();

export default withAuthUser({
  whenUnauthedAfterInit: AuthAction.REDIRECT_TO_LOGIN,
})(Materials);

Config:

import { init } from "next-firebase-auth";

const TWELVE_DAYS_IN_MS = 12 * 60 * 60 * 24 * 1000;

const initAuth = () => {
  init({
    debug: true,
    authPageURL: "/login",
    appPageURL: "/materials",
    loginAPIEndpoint: "/api/login", // required
    logoutAPIEndpoint: "/api/logout", // required
    firebaseAdminInitConfig: {
      credential: {
        projectId:
          process.env.NODE_ENV === "production"
            ? process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID!
            : process.env.NEXT_PUBLIC_FIREBASE_DEV_PROJECT_ID!,
        privateKey:
          process.env.NODE_ENV === "production"
            ? process.env.FIREBASE_PRIVATE_KEY!
            : process.env.FIREBASE_DEV_PRIVATE_KEY!,
      },
    },

    firebaseClientInitConfig: {
      apiKey:
        process.env.NODE_ENV === "production"
          ? process.env.NEXT_PUBLIC_FIREBASE_API_KEY!
          : process.env.NEXT_PUBLIC_FIREBASE_DEV_API_KEY!,
      authDomain:
        process.env.NODE_ENV === "production"
          ? process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN
          : process.env.NEXT_PUBLIC_FIREBASE_DEV_AUTH_DOMAIN,
      projectId:
        process.env.NODE_ENV === "production"
          ? process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID
          : process.env.NEXT_PUBLIC_FIREBASE_DEV_PROJECT_ID,
    },
    cookies: {
      name: "Project",
      keys: [
        process.env.COOKIE_SECRET_CURRENT,
        process.env.COOKIE_SECRET_PREVIOUS,
      ],
      httpOnly: false,
      path: "/",
      maxAge: TWELVE_DAYS_IN_MS,
      overwrite: true,
      sameSite: "lax",
      secure: process.env.NEXT_PUBLIC_COOKIE_SECURE === "true",
      signed: true,
    },
  });
};

export default initAuth;

Versions

next-firebase-auth version: 1.0.0-canary.18 Firebase JS SDK: 9.12.1 Next.js: latest

To Reproduce After logging in, i get the next-firebase-auth: [getUserFromCookies] Failed to retrieve the ID token from cookies. This will happen if the user is not logged in, the provided cookie values are invalid, or the cookie values don't align with your cookie settings. The user will be unauthenticated. error, which redirects me to the login page, which in turn redirects me to the protected page again, creating an endless loop. When inspecting the cookies, I do see that they are being written, so I'm not sure where it is going wrong at the moment.

agdolla commented 1 year ago

I have the same problem.

agdolla commented 1 year ago

I also get: next-firebase-auth: [verifyIdToken] Successfully verified the ID token. The user is authenticated. next-firebase-auth: [setAuthCookies] Getting a refresh token from the ID token. next-firebase-auth: [setAuthCookies] Failed to verify the ID token. Cannot authenticate the user or get a refresh token. next-firebase-auth: [setAuthCookies] Set auth cookies. The user is not authenticated.

agdolla commented 1 year ago

@Bowis now it works for me, the trick is to define your FIREBASE_PRIVATE_KEY like this FIREBASE_PRIVATE_KEY='""' so that JSON.parse can parse it