awinogrodzki / next-firebase-auth-edge

Next.js Firebase Authentication for Edge and Node.js runtimes. Compatible with latest Next.js features.
https://next-firebase-auth-edge-docs.vercel.app/
MIT License
461 stars 41 forks source link

INVALID_CREDENTIAL error when refreshing Server Cookies #213

Closed VVhispo closed 1 month ago

VVhispo commented 1 month ago

Hey, I use custom email handlers for Firebase account management operations like reset password or update email address. I pass the action code to a server action and use it there calling applyActionCode() function from firebase/auth. After that I try to refresh user's credentials with refreshServerCookies() and that throws an 'INVALID_CREDENTIAL' error every single time. My code:

'use server'

import { cookies, headers } from "next/headers";
import { redirect } from "next/navigation";
import { refreshServerCookies } from "next-firebase-auth-edge/lib/next/cookies";
import { applyActionCode, checkActionCode } from "firebase/auth";
import { getTokens } from "next-firebase-auth-edge";
import { getFirebaseAuth } from "@/config/firebase";
import { authConfig } from "@/config/server-config";

export const verifyEmailUpdate = async (code: string) => {
  if (!code) throw new Error("No code provided");

  const res = await checkActionCode(getFirebaseAuth(), code);

  if (!res.data.email && !res.data.previousEmail)
    throw new Error("Wrong action code for this operation");

  await applyActionCode(getFirebaseAuth(), code);

  const auth = await getTokens(cookies(), authConfig);
  if (!auth) return redirect("/sign-in");
  else {
    await refreshServerCookies(cookies(), headers(), authConfig);
    return true;
  }
};

Error log:

AuthError: Invalid credentials: Error fetching access token: {"code":400,"message":"TOKEN_EXPIRED","status":"INVALID_ARGUMENT"} 
    at refreshExpiredIdToken (webpack-internal:///(rsc)/./node_modules/next-firebase-auth-edge/lib/auth/index.js:80:15)
    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
    at async handleTokenRefresh (webpack-internal:///(rsc)/./node_modules/next-firebase-auth-edge/lib/auth/index.js:124:60)
    at async refreshNextCookies (webpack-internal:///(rsc)/./node_modules/next-firebase-auth-edge/lib/next/cookies.js:165:31)
    at async refreshServerCookies (webpack-internal:///(rsc)/./node_modules/next-firebase-auth-edge/lib/next/cookies.js:207:31)
    at async $$ACTION_0 (webpack-internal:///(rsc)/./app/actions/verifyEmailUpdate.ts:43:9)
    at async ChangeEmail (webpack-internal:///(rsc)/./app/(authentication)/changeEmail/page.tsx:18:24) {
  code: 'INVALID_CREDENTIAL'
}
awinogrodzki commented 1 month ago

Hey @VVhispo!

Thanks for reporting.

I have added slightly modified version of your verifyEmailUpdate function in this example: https://github.com/awinogrodzki/next-firebase-auth-edge/pull/215/files

It seems to be working as expected

TOKEN_EXPIRED error in during token refresh usually happens due to discrepancy between Google and server times. Could you verify that you have correct time setup in machine where you're running the server?

VVhispo commented 1 month ago

It's definitely not an issue with time discrepancy because other server actions in my app work fine. The example you provided applies the action code on button click in a client component. What I'm trying to do is apply the code right after redirecting to the page, in a server component.

page.tsx

export default async function ChangeEmail({
  searchParams,
}: {
  searchParams?: { [key: string]: string | string[] | undefined };
}) {
  const code = searchParams?.code;
  if (!code || typeof code !== "string") redirect("/");
  try {
    const result = await verifyEmailUpdate(code);
  } catch (err) {
    console.log(err);
    return <Result success={false} />;
  }

  return <Result success={true} />;
}
awinogrodzki commented 1 month ago

Which version of Next.js are you using?

I have added code verification page: https://github.com/awinogrodzki/next-firebase-auth-edge/pull/215

refreshServerCookies fails with following message:

[Error]: Cookies can only be modified in a Server Action or Route Handler. Read more: https://nextjs.org/docs/app/api-reference/functions/cookies#cookiessetname-value-options
    at Proxy.callable (/Users/amadeusz/Projects/next-firebase-auth-edge/examples/next-typescript-starter/node_modules/next/dist/compiled/next-server/app-page.runtime.dev.js:36:12959)
    at eval (webpack-internal:///(rsc)/../../lib/next/cookies.js:91:17)
    at generateCookies (webpack-internal:///(rsc)/../../lib/next/cookies.js:69:5)
    at appendCookies (webpack-internal:///(rsc)/../../lib/next/cookies.js:90:5)
    at MultiHeaderVerifier.appendCookies (webpack-internal:///(rsc)/../../lib/next/cookies.js:294:9)
    at refreshServerCookies (webpack-internal:///(rsc)/../../lib/next/cookies.js:349:14)
    at async $$ACTION_0 (webpack-internal:///(rsc)/./app/profile/UserProfile/verify-email-update.ts:35:9)
    at async VerifyCode (webpack-internal:///(rsc)/./app/code/page.tsx:16:9)

The error states that cookies can be only updated inside Server Actions or Route Handlers. AFAIK Server Actions should be called from inside client components in order to be able to update cookies

awinogrodzki commented 1 month ago

I hope the issue is solved. Feel free to re-open if otherwise