supabase / auth-helpers

A collection of framework specific Auth utilities for working with Supabase.
https://supabase.github.io/auth-helpers/
MIT License
893 stars 240 forks source link

@Supabase/SSR still showing user and cookie despite using signOut() in nextjs 14 #688

Closed dukesx closed 7 months ago

dukesx commented 7 months ago

Bug report

Describe the bug

I have set up middleware, client and server clients according to Nextjs and Supabase Docs but when i sign out, i still get user and session.

I am using supabase.auth.getSession() on a server component to get session and pass it to a client component. When i click "sign out" , i await supabase.auth.signOut() and then reload the page using window object. I still get to see the session and user objects which should not be the case. I have checked, cookies are CLEARED.

To Reproduce

Fetch Session on a server component. Login using oAuth and then sign out and lastly reload page.

Expected behavior

Session and user Objects should not exist considering that cookies are cleared.

Screenshots

If applicable, add screenshots to help explain your problem.

System information

dukesx commented 7 months ago

If anyone encounters this issue, here is the solution: https://www.youtube.com/watch?v=PdmKlne1gRY and Supabase, PLEASE, before you ask devs to migrate to new packages, make sure you have COMPLETE documentation and resources laid out.

hydenp commented 5 months ago

Also came across this issue and was confused as to why this wasn't working. I also tried the above solution without any luck.

Originally defined the signout function in the component with the "user server" like in the above video

something like the following

export default async function LoginPage() {
  const cookieStore = cookies()
  const supabase = createClient(cookieStore)

  const { data, error } = await supabase.auth.getUser()

  async function logout(event: Event) {
    "user server"
    await supabase.auth.signOut()
  }

  return data?.user ? (
    <>
      <p>Hello {data.user.email}</p>
      <form action={logout}>
        <button>Logout yall</button>
      </form>
    </>
  ) : (
    <form>
      <label htmlFor='email'>Email:</label>
      <input id='email' name='email' type='email' required />
      <label htmlFor='password'>Password:</label>
      <input id='password' name='password' type='password' required />
      <button formAction={login}>Log in</button>
      <button formAction={signup}>Sign up</button>
    </form>
  )
}

This still confused Next. I think problem is you can't define a server running function within a server component. All I had to do was move my logout function outside the component.

If you're following along with the SSR next auth tutorial here, I just added it to the app/login/actions.ts page


...

export async function signup(formData: FormData) {
  const cookieStore = cookies()
  const supabase = createClient(cookieStore)

  // type-casting here for convenience
  // in practice, you should validate your inputs
  const data = {
    email: formData.get('email') as string,
    password: formData.get('password') as string,
  }

  const { error } = await supabase.auth.signUp(data)

  if (error) {
    redirect('/error')
  }

  revalidatePath('/', 'layout')
  redirect('/')
}

export async function logout() {
  const cookieStore = cookies()
  const supabase = createClient(cookieStore)

  await supabase.auth.signOut()

  revalidatePath('/', 'layout')
  redirect('/auth')
}

And then in my auth/page.tsx


import { login, signup, logout } from './actions'
import { cookies } from 'next/headers'
import { createClient } from '@/utils/supabase/server'

export default async function LoginPage() {
  const cookieStore = cookies()
  const supabase = createClient(cookieStore)

  const { data, error } = await supabase.auth.getUser()

  return data?.user ? (
    <>
      <p>Hello {data.user.email}</p>
      <form action={logout}>
        <button>Logout yall</button>
      </form>
    </>
  ) : (
    <form>
      <label htmlFor='email'>Email:</label>
      <input id='email' name='email' type='email' required />
      <label htmlFor='password'>Password:</label>
      <input id='password' name='password' type='password' required />
      <button formAction={login}>Log in</button>
      <button formAction={signup}>Sign up</button>
    </form>
  )
}

Maybe super obvious in hindsight, but I didn't find it clear why the above wasn't working for me originally. Maybe this is helpful to someone.

jj0b commented 3 months ago

@hydenp I just followed the same Server-Side Auth Next.js instruction from Supabase but am having an issue that my sb-localhost-auth-token cookie is not being set. This means that even though I authenticate without issue, because the cookie is never set, I can't getUser or getSession successfully any of the places that they do in the tutorial.

I notice that you are doing something different from the tutorial, which is that you are passing the cookies into createClient instead of doing that in utils/supabase/server like they do in the tutorial. Is there a reason for that?

I've literally copy and pasted from the tutorial and am having this major problem, so wondering if you had to do anything else to get this to work for you?

DAGGE3R commented 3 months ago

@jj0b , I'm not sure if this will be helpful, but I'd like to share my experience. Initially, I was creating a constant outside and calling it in every function to simplify the code. The constant was defined as follows ( as a global variable) :

const supabase = createClient();

However, I realized that this approach was incorrect. It seems that you need to create it every time you create an action. This solution fixed the issue for me. It’s possible that this information was included in the documentation, but I may have overlooked it. I hope you find this information useful.

jj0b commented 3 months ago

@DAGGE3R thank you. Yes, I did see it in the docs here that you need to create the client anew for every request to the server because it needs the cookies for the request.

Screenshot 2024-03-13 at 2 57 46 PM

I think this explains why @hydenp was passing the cookieStore to createClient, but in the Supabase tutorial I linked they just create the client from scratch in multiple different places.

Turns out my issue was simply that I was using the domain 127.0.0.1 in my callback url instead of localhost which is where I was running my app. Even though that IP resolves to the same thing, it meant that my cookie was being written to the wrong domain. Once I updated my callback url to be at localhost everything worked.

mlynch commented 2 months ago

EDIT: I'm new to app router and wasn't understanding a core concept of it, so I've fixed this issue. If you're like me and prefer to have a /logout route that logs the user out and redirects them, then make sure to put that into a router handler, NOT a page. This is all you need.

app/logout/route.ts

import { createClient } from "@/utils/supabase/server";
import { redirect } from "next/navigation";

export async function GET(request: Request) {
  const supabase = createClient();

  await supabase.auth.signOut();

  return redirect('/');
}

My old (incorrect) issue: I'm seeing the same issue after following the Supabase Auth + Next.js guide here: https://supabase.com/docs/guides/auth/server-side/nextjs

I have a /logout route that just calls signOut but none of the cookies are getting removed so the user is effectively never logged out.

import { createClient } from "@/utils/supabase/server";
import { revalidatePath } from "next/cache";
import { redirect } from "next/navigation";

export default async function Logout() {
  const supabase = createClient();

  await supabase.auth.signOut();

  revalidatePath("/", "layout");
  return redirect('/');
}

Navigating to /logout and the user is not logged out. I have an older app using the Next.js pages router which has effectively the same code as below, and it works correctly so this seems to just be an app router + supabase issue.