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

Cookies not setting properly supabase ssr #22719

Open
Sof2222 opened this issue Apr 14, 2024 · 6 comments
Open

Cookies not setting properly supabase ssr #22719

Sof2222 opened this issue Apr 14, 2024 · 6 comments
Labels
auth All thing Supabase Auth related bug Something isn't working

Comments

@Sof2222
Copy link

Sof2222 commented Apr 14, 2024

Bug report

I have already checked and cant see the same issue.

  • [x ] I confirm this is a bug with Supabase, not with my own application.
  • [x ] I confirm I have searched the Docs, GitHub Discussions, and Discord.

Describe the bug

I am using supabase-ssr package to log on.

I thought this was only an issue in dev mode as when I ran build mode on Friday it worked, but perhaps I had not properly deleted the cookie when I was testing so am getting the error again now.

Basically the auth-token cookie is not setting properly. If I log on twice, it sets but the first time i log on only sb-__-auth-token-code-verifier is set.
I am unsure if it is something on my side which is causing the error or if there is something timing out in the setting of the second cookie. My code is below.
Note I am using a otp sent to emails for this.

To Reproduce

This is to get the code:

export async function signuplogin(prevState: any, formData: FormData) {
  console.log(formData);
  const validatedFields = schema.safeParse({
    email: formData.get("email"),
  });
  console.log(validatedFields);

  if (!validatedFields.success) {
    console.log(validatedFields.error.flatten().fieldErrors.email);
    return {
      message: validatedFields.error.flatten().fieldErrors,
    };
  }
  console.log(formData);
  const email = formData.get("email") as string;
  return signInOTP({ email });

}

This is the server component for createClient:

import { createServerClient, type CookieOptions } from "@supabase/ssr";
import { cookies } from "next/headers";

export const createClient = (cookieStore: ReturnType<typeof cookies>) => {
  return createServerClient<Database>(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
    {
      cookies: {
        get(name: string) {
          return cookieStore.get(name)?.value;
        },
        set(name: string, value: string, options: CookieOptions) {
          try {
            cookieStore.set({ name, value, ...options });
          } catch (error) {
            // The `set` method was called from a Server Component.
            // This can be ignored if you have middleware refreshing
            // user sessions.
          }
        },
        remove(name: string, options: CookieOptions) {
          try {
            cookieStore.set({ name, value: "", ...options });
          } catch (error) {
            // The `delete` method was called from a Server Component.
            // This can be ignored if you have middleware refreshing
            // user sessions.
          }
        },
      },
    }
  );
};

This is to check the OTP code:

export async function precheckOTP(prevState: any, formData: FormData) {
  console.log(formData);
  const validatedFields = schema.safeParse({
    code: formData.get("code"),
  });
  console.log(validatedFields);

  if (!validatedFields.success) {
    console.log(validatedFields.error.flatten().fieldErrors.code);
    return {
      message: validatedFields.error.flatten().fieldErrors,
    };
  }
  console.log(formData);
  const code = formData.get("code") as string;
  const user = formData.get("user") as string;
  return checkOTP({ token: code, user: user });
}


const checkOTP = async ({ token, user }: { token: string; user: string }) => {
  const cookieStore = cookies();
  const supabase = createClient(cookieStore);
  const email = atob(user);

  try {
    const { error } = await supabase.auth.verifyOtp({
      email,
      token,
      type: "email",
      options: {
        redirectTo: "/dashboard",
      },
    });

    if (error) {
      return {
        message: { error: "Something went wrong. Please try again." },
      };
    }
  } catch (error) {
    console.log(error);
    return {
      message: { error: "Something went wrong. Please try again." },
    };
  }

  return redirect("/dashboard");
};

I am redirected to the dashboard.

However the cookies are not being set properly. The first time:

sb--auth-token-code-verifier is set properly.
The second time I log on sb-
-auth-token is set

(Note this is called when someone is on protected:

export async function updateSession(request: NextRequest) {
  let response = NextResponse.next({
    request: {
      headers: request.headers,
    },
  });

  const supabase = createServerClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
    {
      cookies: {
        get(name: string) {
          return request.cookies.get(name)?.value;
        },
        set(name: string, value: string, options: CookieOptions) {
          request.cookies.set({
            name,
            value,
            ...options,
          });
          response = NextResponse.next({
            request: {
              headers: request.headers,
            },
          });
          response.cookies.set({
            name,
            value,
            ...options,
          });
        },
        remove(name: string, options: CookieOptions) {
          request.cookies.set({
            name,
            value: "",
            ...options,
          });
          response = NextResponse.next({
            request: {
              headers: request.headers,
            },
          });
          response.cookies.set({
            name,
            value: "",
            ...options,
          });
        },
      },
    }
  );

  await supabase.auth.getUser();

  return { supabase, response };
}

I have tried with our without this: await supabase.auth.getUser();

But then what happens is I get thrown from the route a moment later or if I try to navigate and I am thrown out of the protected route.
I then have to log in again in which case the second cookie is set.

Expected behavior

That the cookies would all set in the first instance and the user is not required to log on twice for them to set

Screenshots

If applicable, add screenshots to help explain your problem.

System information

  • OS: [e.g. macOS,]
  • Browser (if applies) [e.g., safari and firefox]
  • Version of supabase-js: [e.g. 2.42.3] (ssr 0.0.10)
  • Version of Node.js: [e.g. 18.17.0]

nextjs version - 14.1.4

Additional context

Add any other context about the problem here.

@Sof2222 Sof2222 added the bug Something isn't working label Apr 14, 2024
@aaa3334
Copy link

aaa3334 commented Apr 22, 2024

Anyone have an idea or have the same issue? It is constantly happening so its either something in my sign up flow (which as far as I can tell is identical to the docs but I could be wrong), or an issue on supabase side.. either way would 1) need better docs or 2) is a bug in the ssr package which we have all been repeatedly told to upgrade to

@charislam charislam added the auth All thing Supabase Auth related label Apr 22, 2024
@simonha9
Copy link
Contributor

Can take a look at this

@simonha9
Copy link
Contributor

Ok - I am unable to repro this, I am following the docs here and here , have tried both a magiclink and OTP. afaik whether you auth.signup/signin with OTP you should get a session in the returned data , are you able to call auth.getUser() in the protected route after signup (1st or 2nd time)? and what do you get?

Also if anyone else more familiar wants to jump in please feel free to.

@aaa3334
Copy link

aaa3334 commented May 1, 2024

So I did quite a bit more investigation (still have no clue where the issue is) - tested in both safari and firefox. Same issue - first logon pass it doesnt set the auth cookie properly, the second it does (and it stays set too).

The auth-token is definitly coming through from request in the first instance - it is just not being set properly and i don't really know where it is being set.. I have tried to go through the code but I cannot see anywhere I would be eg. removing it (and if it was, it stays after the second log on everytime anyway). (only the auth-token-code-verifier is being set first pass)

I did upgrade a while back from using auth-helper and js, so there could potentially be some legacy code from that, but I cannot find anything obvious at least and don't have those imports in my code anymore either.

I have added a bunch of console.logs to the updateSession function to try and follow and see where the issues are. I also tried to set it manually (but when I set it manually it was actually removed (i saw the remove statements) the first pass then set properly the second. While the get function is definitly called many times (and especially on the first pass get finds auth-token) it is not finding auth-token upon page refresh.. (so I am guessing it is uing the headers originally, as it also does go to the dashboard but is just not being set properly).

I cannot see anywhere I am deleting this cookie and have searched everywhere I am accessing the cookies too.

I am not sure where the set functions are being called though - I don't get any console.logs from there at all... The remove I only ever saw 1 console log which was when I tried to set it myself manually. (even when the cookies are definitly being set). Is that on a different part of the supabase client maybe?

export async function updateSession(request: NextRequest) {
  let response = NextResponse.next({
    request: {
      headers: request.headers,
    },
  });
  // console.log("request.cookies", request.cookies);
  // console.log("response", response);
  console.log("request", request);
  console.log(
    "auth cookie investigation",
    request.cookies.get("sb-...-auth-token")?.value
  );
  const cookieval = request.cookies.get(
    "sb-...-auth-token"
  )?.value;
  const cookievaljson = JSON.parse(cookieval || "{}");
  console.log("auth cookie investigation json", cookievaljson.access_token);
  console.log(
    "auth cookie investigation options",
    request.cookies.get("sb-...-auth-token")
  );

  // request.cookies.set({
  //   name: "sb-cmhsbvzpocwhojvycyin-auth-token",
  //   value: cookievaljson.access_token || "",
  // });
  const supabase = createServerClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
    {
      cookies: {
        get(name: string) {
          console.log("cookies name", name);
          console.log("cookies value", request.cookies.get(name)?.value);

          return request.cookies.get(name)?.value;
        },
        set(name: string, value: string, options: CookieOptions) {
          console.log(
            `Setting cookie ${name} to ${value} with options ${JSON.stringify(
              options
            )}`
          );
          request.cookies.set({
            name,
            value,
            ...options,
          });
          response = NextResponse.next({
            request: {
              headers: request.headers,
            },
          });
          response.cookies.set({
            name,
            value,
            ...options,
          });
          console.log("response after setting cookie", response);
        },
        remove(name: string, options: CookieOptions) {
          console.log("cookies removing name", name);
          console.log("cookies removing options", options);
          request.cookies.set({
            name,
            value: "",
            ...options,
          });
          response = NextResponse.next({
            request: {
              headers: request.headers,
            },
          });
          response.cookies.set({
            name,
            value: "",
            ...options,
          });
        },
      },
    }
  );

  const user = await supabase.auth.getUser();
  console.log("User:", user);
  // console.log("Supabase:", supabase);

  return { supabase, response };
}

@simonha9
Copy link
Contributor

simonha9 commented May 2, 2024

Ok super weird - I thought I was getting the error for about an hour but now it doesn't seem to want to reproduce, afaict there isn't an issue with the auth code exchange, and it only takes me 1 signup or login to see both cookies, if you upgraded from the recent auth-helpers it sounds like the main difference is the way the cookies and its properties are managed, there is this example of supabase auth with ssr, but from the above middleware I can't find anything wrong with it. Maybe it could be something to do with the legacy code but it sounds ilke you've done a full migration :/

There is a possibly related ticket here but it's hard to tell 🤷 ,

@oldbettie
Copy link

I have a similar issue. I think it has to do with how you are setting the cookies. For some reason we can only set a single Set-Cookie header with next.js I have tried all the different ways to do it and it seems to be a constant issue.

If there is more then 1 cookie in the header then it seems to break the auth flow. I am trying to set a cookie for sharing auth with other micro services but it does not work and I have not figured out how to modify the auth cookie to be a base level domain try only setting a single cookie once per request

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
auth All thing Supabase Auth related bug Something isn't working
Projects
None yet
Development

No branches or pull requests

5 participants