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
504 stars 43 forks source link

INVALID_CUSTOM_TOKEN error when trying to set cookie #31

Closed FabianRueckert closed 1 year ago

FabianRueckert commented 1 year ago

As soon as I call

    const tokenResult = await firebaseUser.getIdTokenResult();
    await fetch("/api/login", {
      method: "GET",
      headers: {
        Authorization: `Bearer ${tokenResult.token}`,
      },
    });

This console error message shows up:

error - node_modules/next-firebase-auth-edge/lib/auth/index.js (42:0) @ customTokenToIdAndRefreshTokens
error - Problem getting a refresh token: {"error":{"code":400,"message":"INVALID_CUSTOM_TOKEN","errors":[{"message":"INVALID_CUSTOM_TOKEN","domain":"global","reason":"invalid"}]}}

And no cookie is set. tokenResult contains a valid token however. Any idea what causes this? I am on firebase 9.18.0, firebase-admin 11.5.0, next 13.2.4, "next-firebase-auth-edge": "0.5.1" with authentification middleware.

awinogrodzki commented 1 year ago

Hey @FabianRueckert,

Thanks for reporting the issue. INVALID_CUSTOM_TOKEN is returned from Firebase for malformed or expired tokens during token refresh. I will try to reproduce the issue with versions you have provided and get back to you

FabianRueckert commented 1 year ago

I am using cloud firebase.

awinogrodzki commented 1 year ago

@FabianRueckert what method are you using to login before calling const tokenResult = await firebaseUser.getIdTokenResult();?

FabianRueckert commented 1 year ago

I am using StyledFirebaseAuth from firebaseui-web-react

awinogrodzki commented 1 year ago

Hey @FabianRueckert!

I created firebaseui example with all the dependencies and versions you mentioned above: https://github.com/awinogrodzki/next-firebase-auth-edge/commit/7d325701a51124e89a9fc1379c3819411354188c

Unfortunately, I wasn't able to reproduce INVALID_CUSTOM_TOKEN error.

Could you verify if the example works for you locally or it fails with the same error? If so, probably the issue is connected with configuration.

By the way, could you post me the configuration you are passing to authentication middleware?

awinogrodzki commented 1 year ago

@FabianRueckert I will close the issue. Please let me know if the provided example works when you have a chance to test it. If the problem continues we will reopen the issue 👍

bruno-de-queiroz commented 6 months ago

I'm experiencing the same issue, suddenly with no code changes I started receiving

node_modules/next-firebase-auth-edge/lib/auth/index.js (56:0) @ customTokenToIdAndRefreshTokens
 ⨯ Problem getting a refresh token: {"error":{"code":400,"message":"INVALID_CUSTOM_TOKEN","errors":[{"message":"INVALID_CUSTOM_TOKEN","domain":"global","reason":"invalid"}]}}
null

I'm not using firebaseui, and this is the IdTokenChangedHandler:

  async (firebaseUser: FirebaseUser | null) => {
    if (firebaseUser !== null) {
      const idTokenResult = await firebaseUser.getIdTokenResult();
      await fetch("/api/login", {
        headers: {
          Authorization: `Bearer ${idTokenResult.token}`,
        },
      });
      setCurrentUser(fromClient(firebaseUser, idTokenResult));
      if (redirect) {
        router.push(redirect, { scroll: true });
      } else {
        router.refresh();
      }
      return;
    } else if (currentUser !== undefined) {
      await fetch("/api/logout");
      setCurrentUser(undefined);
      router.refresh();
    }
  }
awinogrodzki commented 6 months ago

Hey @bruno-de-queiroz!

Could you run npx next info in project root and paste the result here?

What version of next-firebase-auth-edge are you using?

Could you reproduce the issue with debug mode enabled and share some logs?

bruno-de-queiroz commented 6 months ago

Sure, here we go:

$ npx next info

Operating System:
  Platform: linux
  Arch: x64
  Version: #1 SMP Tue Dec 19 21:36:30 -03 2023
Binaries:
  Node: 20.3.1
  npm: 9.6.7
  Yarn: N/A
  pnpm: N/A
Relevant Packages:
  next: 14.1.4
  eslint-config-next: 14.1.4
  react: 18.2.0
  react-dom: 18.2.0
  typescript: 5.4.3
Next.js Config:
  output: N/A

And the debug logs:

ⓘ next-firebase-auth-edge: Handle request
         path: /api/login
ⓘ next-firebase-auth-edge: Handle authentication API route
ⓘ next-firebase-auth-edge: Handle request
         path: /en/sign-in
ⓘ next-firebase-auth-edge: Missing authentication cookies
ⓘ next-firebase-auth-edge: Token is missing or has incorrect formatting. This is expected and usually means that user has not yet logged in
         reason: MISSING_CREDENTIALS
ⓘ next-firebase-auth-edge: Get public keys from cache
         expiresAt: 1711997683685
         now: 1711975626219
         digest: ev22VX2HDM4NRfoHlylb6lofBhpLXvQiHzCrCpGWf7M
         cryptoRuntime: WebCryptoAPI
ⓘ next-firebase-auth-edge: Generated custom token based on provided idToken
         customToken: <redacted>
ⓘ next-firebase-auth-edge: Missing authentication cookies
ⓘ next-firebase-auth-edge: Missing authentication cookies
ⓘ next-firebase-auth-edge: Token is missing or has incorrect formatting. This is expected and usually means that user has not yet logged in
         reason: MISSING_CREDENTIALS
ⓘ next-firebase-auth-edge: Token is missing or has incorrect formatting. This is expected and usually means that user has not yet logged in
         reason: MISSING_CREDENTIALS
 ⨯ node_modules/next-firebase-auth-edge/lib/auth/index.js (56:0) @ customTokenToIdAndRefreshTokens
 ⨯ Problem getting a refresh token: {"error":{"code":400,"message":"INVALID_CUSTOM_TOKEN","errors":[{"message":"INVALID_CUSTOM_TOKEN","domain":"global","reason":"invalid"}]}}
null

and version:


    "next-firebase-auth-edge": "^1.4.2",```
awinogrodzki commented 6 months ago

Thanks!

As official docs suggest: INVALID_CUSTOM_TOKEN: The custom token format is incorrect or the token is invalid for some reason (e.g. expired, invalid signature etc.)

I would need to examine your generated custom token:

ⓘ next-firebase-auth-edge: Generated custom token based on provided idToken
         customToken: <redacted>

Could you share the value behind <redacted>?

If there's some data in the token you wish not to share, you can use https://jwt.io/ to decode the token and obfuscate some fields. I am most interested in JWT header, kid and expiry dates

bruno-de-queiroz commented 6 months ago

image

Maybe that helps, let me know :)

Funny not seeing a kid in the header

bruno-de-queiroz commented 6 months ago

Apparently, the token has expired already, although I cleared the storages, cookies, etc and started the sign-in with firebase/auth

bruno-de-queiroz commented 6 months ago

Not really, I'm using firebase/auth for the client-side signIn, with a real firebase account pointing to a live project

export const signIn = async (params: SignInParams) => {
  switch (params.provider) {
    case "google":
      await signInWithPopup(
        getAuth(),
        getGoogleProvider(params.locale),
        browserPopupRedirectResolver,
      );
      break;
    case "email":
      await signInWithEmailAndPassword(
        getAuth(),
        params.email,
        params.password,
      );
      break;
  }
};
bruno-de-queiroz commented 6 months ago

Just for the sake: here is the full log line with the token:

ⓘ next-firebase-auth-edge: Generated custom token based on provided idToken
         customToken: eyJhbGciOiJSUzI1NiJ9.eyJhdWQiOiJodHRwczovL2lkZW50aXR5dG9vbGtpdC5nb29nbGVhcGlzLmNvbS9nb29nbGUuaWRlbnRpdHkuaWRlbnRpdHl0b29sa2l0LnYxLklkZW50aXR5VG9vbGtpdCIsImlhdCI6MTcxMjAwOTcxMiwiZXhwIjoxNzEyMDEzMzEyLCJpc3MiOiJ
maXJlYmFzZS1hZG1pbnNkay03NTQyYkBuZWVkbGVzLWM3OTM5LmlhbS5nc2VydmljZWFjY291bnQuY29tIiwic3ViIjoiZmlyZWJhc2UtYWRtaW5zZGstNzU0MmJAbmVlZGxlcy1jNzkzOS5pYW0uZ3NlcnZpY2VhY2NvdW50LmNvbSIsInVpZCI6IjV4QVRkQ1daa3ZXRFVXU3Yxa0VDVFJFellTRzIiLCJjb
GFpbXMiOnsiZW1haWxfdmVyaWZpZWQiOnRydWUsInNvdXJjZV9zaWduX2luX3Byb3ZpZGVyIjoiZ29vZ2xlLmNvbSJ9fQ.piA5cFl0D82zZlEB4g4XGokJPCHztFsrMir-TlJZh5qpbEYL2JqLlK2kTDnxOUkUvG7yRQh-ldB0ZV3o5vg7xMNjS5KZcppN_SN_DKfagXIHqcH7GSzx9ObYdHIHUnR-Wzf7aNBOdn3NkeJKqYhgI-WndIzTwbjWwALPvJylVCyb2Y_cqBS2qCrJ_X1fpcuoFPhxChLdmIsFck5jtOpBBknWgmKmZ_db5IfGrlOmfNfUwyRvmzNFu2rXMesgVbBJ-958V2HizNukrTJt7x1yD4A1McfoxKkrLZQuldORWeCFs8lHWHzIN1E5zpcWvIHzpRZTIWkRbPXJAxJwMw0o4Q
 ⨯ node_modules/next-firebase-auth-edge/lib/auth/index.js (56:0) @ customTokenToIdAndRefreshTokens
 ⨯ Problem getting a refresh token: {"error":{"code":400,"message":"INVALID_CUSTOM_TOKEN","errors":[{"message":"INVALID_CUSTOM_TOKEN","domain":"global","reason":"invalid"}]}}
null
awinogrodzki commented 6 months ago

Thanks!

Sorry, I confirmed that the customToken has in fact the correct structure. It might be the expiry dates.

Are these logs from today? I've noticed that iat and now is about 1-2 days in the past.

Are you running the project locally? Is your system time up to date?

bruno-de-queiroz commented 6 months ago

I've added a console.log to print the credentials after the signIn, and the token in the user.accessToken decoded has the same format as the example you posted. So that customToken does not follow the original format.

bruno-de-queiroz commented 6 months ago

Hmm I'm running local with WSL2, so I just ran wsl --shutdown to restart the service and voilá, it is working again 🤦

bruno-de-queiroz commented 6 months ago

Thanks @awinogrodzki for the debug session, but this is very interesting indeed.

awinogrodzki commented 6 months ago

No worries, glad we've worked it out!

It's worth to note here that remote authentication is usually time-sensitive process. If iat (issued at) is suspiciously old, we can get rejected by Google.

bruno-de-queiroz commented 6 months ago

Yup, I noticed that when I converted the exp and was pointing to 2 days ago that this was probably the problem, didn't expect WSL2 playing tricks on the system time