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

signOut not deleting cookie #46

Closed
gomflo opened this issue Jan 16, 2021 · 27 comments
Closed

signOut not deleting cookie #46

gomflo opened this issue Jan 16, 2021 · 27 comments

Comments

@gomflo
Copy link

gomflo commented Jan 16, 2021

Bug report

Describe the bug

supabase.auth.signOut() is not deleting the cookie created by setAuthCookie

// /api/login.js

import { supabase } from '@/utils/initSupabase'

export default function handler(req, res) {
  supabase.auth.api.setAuthCookie(req, res)
}
// logout.js

import { supabase } from '@/utils/initSupabase'
import { useRouter } from 'next/router'
import { useEffect } from 'react'

export default function Logout() {
  const router = useRouter()

  useEffect(() => {
    async function initialize() {
      await supabase.auth.signOut()
      router.push('/login')
    }
    initialize()
  })
  return <></>
}

image

@kiwicopple
Copy link
Member

Thanks @gomflo - I'm shifting this to gotrue-js so we can solve it asap 👍

@kiwicopple kiwicopple transferred this issue from supabase/supabase Jan 18, 2021
@gomflo
Copy link
Author

gomflo commented Jan 18, 2021

thanks, the workaround i end up doing was using supabase.auth.onAuthStateChange

// logout.js
await supabase.auth.signOut() // <= this trigger onAuthStateChange
const { data: authListener } = supabase.auth.onAuthStateChange(
      async (event, session) => {
        const body = JSON.stringify({ event, session })
        const headers = new Headers({ 'Content-Type': 'application/json' })

        await fetch('/api/login', {
          method: 'post',
          body,
          headers,
          credentials: 'same-origin',
        })
      }
    )
    return () => {
      authListener.unsubscribe()
    }

this way the cookie gets deleted

@kiwicopple
Copy link
Member

From a cursory look at our implementation, we have a listener on sign out:
https://github.com/supabase/gotrue-js/blob/8b61cc39df22cf9e1d546df7e1d06e9b27c51556/src/GoTrueApi.ts#L217

So we should verify the reason why it isn't working here. I'm guessing this can be replicated in our Nextjs example
https://github.com/supabase/gotrue-js/blob/master/example/next/README.md

@mrkldshv
Copy link

@kiwicopple Actually, I was able to reproduce it trying to sign out on any SSRed page. Not sure if that can be the reason, but maybe that's something we should look into.
@gomflo Is the page you trying sing out on using SSR?

@gomflo
Copy link
Author

gomflo commented Jan 24, 2021

@gomflo Is the page you trying sing out on using SSR?

I have a page named logout.js:

carbon (1)

And from any page i have the following tag to logout:

<a href="/logout">Logout</a>

Thats my login.js page using magic link to sign in

const onSubmit = async ({ email }) => {
  const { error } = await supabase.auth.signIn({ email })
 }

Then when te user click the magic link i have the following page loginValidate.js

import { useEffect } from 'react'
import { useRouter } from 'next/router'
import { supabase } from '@/utils/initSupabase'

export default function LoginValidate() {
  const router = useRouter()

  useEffect(() => {
    const { data: authListener } = supabase.auth.onAuthStateChange(
      async (event, session) => {
        const body = JSON.stringify({ event, session })

        await fetch('/api/login', {
          method: 'post',
          headers: new Headers({ 'Content-Type': 'application/json' }),
          credentials: 'same-origin',
          body
        })

        router.push('/dashboard')
      }
    )

    return () => {
      authListener.unsubscribe()
    }
  }, [])

  return <></>
}

Finally my /api/login.js file

import { supabase } from '@/utils/initSupabase'

export default function handler(req, res) {
  supabase.auth.api.setAuthCookie(req, res)
}

@mrkldshv
Copy link

@gomflo Thanks for posted example! I tried it and it failed for me as well. Any guesses @kiwicopple?

@kiwicopple
Copy link
Member

I've modified the Next.js example on this branch: #56

As far as I can tell everything is working well.

Screen.Recording.2021-02-15.at.1.35.32.PM.mov

I'll have to dig into your particular implementation @gomflo and see what the differences are

@tanshunyuan
Copy link

I am facing the same issue when trying to logout from a SSR page, the cookies isn't cleared. But I think I found a solution, that is to make a api call when signing out to remove the cookie that is created from setAuthCookie.
Here I am using nookies to remove the cookie
Screenshot 2021-04-16 at 11 52 05 PM

On the client-side, when clicking signOut it is calling /api/signout that will destroy the cookies generated from SSR
Screenshot 2021-04-16 at 11 54 52 PM

@phamhieu
Copy link
Member

phamhieu commented Apr 30, 2021

I am facing the same issue when trying to logout from a SSR page, the cookies isn't cleared.

maybe related to #73
@tanshunyuan Can you confirm if you are using the latest supabase client version?

@tanshunyuan
Copy link

Hi @phamhieu, currently I am using version ^1.11.6

@Ali-Parandeh
Copy link

Ali-Parandeh commented May 2, 2021

Having similar issues as well. I have implemented a signout button in the SSR page calling logout.js api. Sometimes it appears to be working and other times it redirects to the homepage on the sign out only to redirect back to the dashboard page (SSR protected).

logout.ts

import {supabase} from 'libs/initSupabase'

export default async function logoutUser(req, res) {
  const {error} = await supabase.auth.signOut()
  if (error) return res.status(401).json({error: error.message})
  return res.status(200).json({body: 'User has been logged out'})
}

dashboard.tsx

import {CoreProgrammesSection} from '../components/CoreProgrammesSection'
import {DashboardLayout} from '../components/DashboardLayout'
import {DashboardNavBar} from '../components/DashboardNavBar'
import {FeaturedProgrammesSection} from '../components/FeaturedProgrammesSection'
import {NewLessonsSection} from '../components/NewLessonsSection'
import {basePath} from '../utils/siteConfig'

export async function getServerSideProps() {
  const {user} = await fetch(`${basePath}/api/getUser`).then(response =>
    response.json(),
  )

  if (!user) {
    return {
      redirect: {destination: '/', permanent: false},
    }
  }
  // We'll pass the returned `user` to the page's React Component as a prop
  return {props: {user}}
}

export default function Dashboard({user}) {
  return (
    <DashboardLayout user={user}>
      <main className="overflow-y-auto relative z-0 flex-1 focus:outline-none">
        <DashboardNavBar />
        <FeaturedProgrammesSection />
        <CoreProgrammesSection />
        <NewLessonsSection />
      </main>
    </DashboardLayout>
  )
}

dashboardLayout.ts

...

export function DashboardLayout({children, user}) {
  const router = useRouter()
  const [isAuthed, setAuthStatus] = useState(false)

  useEffect(() => {
    if (user) setAuthStatus(true)
  }, [])

  const signOutUser = () => {
    const ElementInterval = setInterval(async () => {
      /* Tracking or other request code goes here */
      const res = await fetch(`/api/logout`)
      if (res.status === 200) setAuthStatus(false)
      // redirect to homepage when logging out users
      if (window.location.href !== '/') await router.push('/')
      clearInterval(ElementInterval)
    }, 50)
  }

  return (
    <div className="flex overflow-hidden h-screen bg-gray-100">
      <MobileSideBar
        router={router}
        sideBarLinks={sideBarLinks}
        signOutUser={signOutUser}
      />
      <DesktopSideBar
        router={router}
        sideBarLinks={sideBarLinks}
        signOutUser={signOutUser}
      />
 ...
  )
}

DesktopSideBar.tsx

....

export function DesktopSideBar({router, sideBarLinks, signOutUser}) {
  return (
....
          <div className="flex flex-shrink-0 py-4 px-6 border-t border-gray-200">
            <div className="w-full text-sm rounded-md text-darkRose hover:bg-soorati group-hover:text-darkRose">
              <button
                className="flex gap-3 justify-center items-center py-3 px-6 pl-0 w-full"
                onClick={signOutUser}
              >
                <HiOutlineLogout className="w-6 h-6" />
                <span className="font-semibold">Logout</span>
              </button>
            </div>
          </div>
....
  )
}

app.js

import '../styles/globals.css'

function MyApp({Component, pageProps}) {
  return <Component {...pageProps} />
}

export default MyApp

@phamhieu
Copy link
Member

phamhieu commented May 3, 2021

After checking the code on gotrue-js for cookie handler, I can confirm that this' a manually process.

It requires manually calling to supabase.auth.api.setAuthCookie(req, res) on signIn/signOut to set/remove cookie
OR you can set/remove cookie manually using any cookie helper package like nookies...

on next-js example, it uses onAuthStateChange subscription to support these manually calls.

// subscription 
supabase.auth.onAuthStateChange(
  async (event, supabaseAuthSession) => {
    await postData('/api/auth', {
      event,
      session: supabaseAuthSession,
    })
  }
)

// /api/auth just triggers setAuthCookie
export default function handler(req, res) {
  supabase.auth.api.setAuthCookie(req, res)
}

@tanshunyuan It's fine to use nookies to remove the cookie manually on signOut. But I wonder how do you set the cookie on signIn?

@Ali-Parandeh On your signOut api, you didn't remove the cookie. May i how do you set the cookie on signIn?

@phamhieu
Copy link
Member

phamhieu commented May 3, 2021

From a cursory look at our implementation, we have a listener on sign out:
https://github.com/supabase/gotrue-js/blob/8b61cc39df22cf9e1d546df7e1d06e9b27c51556/src/GoTrueApi.ts#L217

So we should verify the reason why it isn't working here. I'm guessing this can be replicated in our Nextjs example
https://github.com/supabase/gotrue-js/blob/master/example/next/README.md

setAuthCookie, itself doesn't listen to any event. It acts as a helper method that processes req.body parameters

In next-js example we use supabase.auth.onAuthStateChange to send request to /api/auth which then call supabase.auth.api.setAuthCookie(req, res)

@Ali-Parandeh
Copy link

@phamhieu Thank you for the suggestion!

I followed the Supabase Auth with Next.js SSR example which uses cookies to persist auth between the server and the client.

All is working perfectly now and I can integrate the authentication logic with my custom forms. I was using API routes previously for login, logout etc and wasn't managing session / cookies properly as client state and server state were not being in sync.

@phamhieu phamhieu closed this as completed May 5, 2021
@danielcranney
Copy link

danielcranney commented Dec 13, 2021

@Ali-Parandeh Could you share your solution please? I'm having this exact issue, and saw in your original post you had had issues with it...

@Ali-Parandeh
Copy link

Ali-Parandeh commented Jan 9, 2022

@danielcranney

Here is my solution:
/context/userContext.tsx

import {User} from '@supabase/supabase-js'
import {createContext, useContext, useEffect, useState} from 'react'

import {definitions} from '../types/supabase'
import {supabase} from '../utils/initSupabase'

const UserContext = createContext({
  user: null,
  session: null,
})

export const UserContextProvider = props => {
  const {supabaseClient} = props
  const [session, setSession] = useState(null)
  const [user, setUser] = useState(null)

  useEffect(() => {
    const session = supabaseClient.auth.session()
    setSession(session)
    setUser(session?.user ?? null)
    const {data: authListener} = supabaseClient.auth.onAuthStateChange(
      async (event, session) => {
        setSession(session)
        setUser(session?.user ?? null)
      },
    )
    return () => {
      authListener.unsubscribe()
    }
  }, [])

  const value = {
    session,
    user
  }
  return <UserContext.Provider value={value} {...props} />
}

export const useUser = () => {
  const context = useContext(UserContext)
  if (context === undefined) {
    throw new Error(`useUser must be used within a UserContextProvider.`)
  }
  return context
}

/utils/useAuth.ts

import {useEffect, useState} from 'react'

import {supabase} from './initSupabase'

const postData = (url, data = {}) =>
  fetch(url, {
    method: 'POST',
    headers: new Headers({'Content-Type': 'application/json'}),
    credentials: 'same-origin',
    body: JSON.stringify(data),
  }).then(res => res.json())

const useAuth = () => {
  const [session, setSession] = useState(null)
  const [user, setUser] = useState(null)

  useEffect(() => {
    const supabaseAuthSession = supabase.auth.session()
    setSession(supabaseAuthSession)
    setUser(supabaseAuthSession?.user ?? null)
    const {data: authListener} = supabase.auth.onAuthStateChange(
      async (event, supabaseAuthSession) => {
        console.log(`Supbase auth event: ${event}`)

        await postData('/api/auth', {
          event,
          session: supabaseAuthSession,
        })

        setSession(supabaseAuthSession)
        setUser(supabaseAuthSession?.user ?? null)
      },
    )
    return () => {
      authListener.unsubscribe()
    }
  }, [])

  return {user, session}
}

export {useAuth}

/pages/api/auth.ts

import {supabase} from 'utils/initSupabase'

export default function handler(req, res) {
  supabase.auth.api.setAuthCookie(req, res)
}

/pages/api/getUser.ts

// Next.js API route support: https://nextjs.org/docs/api-routes/introduction

import {supabase} from 'utils/initSupabase'

export default async function getUser(req, res) {
  const token = req.headers.token

  const {data: user, error} = await supabase.auth.api.getUser(token)

  if (error) return res.status(500).json({error: error.message})
  return res.status(200).json(user)
}

/compontents/Auth.tsx

import {useRouter} from 'next/router'
import {useState} from 'react'
import toast from 'react-hot-toast'

function Auth(props) {
  const {supabaseClient, authView, setAuthView} = props

  const [email, setEmail] = useState('')
  const [password, setPassword] = useState('')
  const [message, setMessage] = useState('')
  const [error, setError] = useState('')
  const [loading, setLoading] = useState(false)

  const handleSignIn = async e => {
    e.preventDefault()
    setError('')
    setLoading(true)
    const toastId = toast.loading('Loading...')
    const {error: signInError} = await supabaseClient.auth.signIn({
      email,
      password,
    })

    if (signInError) {
      toast.error(signInError.message, {id: toastId})
      setError(signInError.message)
    } else {
      toast.success('🤘 Successfully signed in! 🤘', {id: toastId})
    }
    setLoading(false)
  }

  const handleSignUp = async e => {
    e.preventDefault()
    setError('')
    setLoading(true)
    const toastId = toast.loading('Loading...')
    const {error: signUpError} = await supabaseClient.auth.signUp({
      email,
      password,
    })

    if (signUpError) {
      toast.error(signUpError.message, {id: toastId})
      setError(signUpError.message)
    } else {
      toast.success('Successfully signed up!', {id: toastId})
    }

    setLoading(false)
  }

  const handlePasswordReset = async e => {
    e.preventDefault()
    setError('')
    setMessage('')
    setLoading(true)
    const toastId = toast.loading('Loading...')
    const {error} = await supabaseClient.auth.api.resetPasswordForEmail(email)
    if (error) {
      toast.error(error.message, {id: toastId})
      setError(error.message)
    } else {
      toast.success('📧 Check your email for the password reset link 📧', {
        id: toastId,
      })
      setMessage('Check your email for the password reset link')
    }
    setLoading(false)
  }

  const handleMagicLinkSignIn = async e => {
    e.preventDefault()
    setError('')
    setMessage('')
    setLoading(true)
    const toastId = toast.loading('Loading...')
    const {error} = await supabaseClient.auth.signIn({email})
    if (error) {
      toast.error(error.message, {id: toastId})
      setError(error.message)
    } else {
      toast.success('🪄📧 Check your email for the magic link 📧🪄', {
        id: toastId,
      })
      setMessage('Check your email for the magic link')
    }
    setLoading(false)
  }

  return (
    <>
      {loading && <h3>Loading..</h3>}
      {error && <div style={{color: 'red'}}>{error}</div>}
      {message && <div style={{color: 'green'}}>{message}</div>}
      {authView === 'sign_in' ? (
        <>
          <h4 className="mt-6 text-2xl text-center font-semibold uppercase">
            Login now
          </h4>
          <form onSubmit={e => handleSignIn(e)}>
            <label
              htmlFor="sign-in__email"
              className="block font-semibold text-black"
            >
              Email
            </label>
            <input
              id="sign-in__email"
              name="Email address"
              autoComplete="email"
              placeholder="Type in your email address"
              defaultValue={email}
              onChange={e => setEmail(e.target.value)}
              className="text-input border pl-4 ring-0 outline-none"
            />
            <label
              htmlFor="sign-in__password"
              className="block font-semibold text-black mt-4"
            >
              Password
            </label>
            <input
              id="sign-in__password"
              name="Password"
              type="password"
              defaultValue={password}
              autoComplete="current-password"
              onChange={e => setPassword(e.target.value)}
              className="text-input pl-4 ring-0 outline-none"
            />
            <button
              type="submit"
              className="py-4 mt-6 w-full font-semibold flex place-content-center space-x-4
              text-white rounded-lg duration-300 transform text-md bg-lightGreen
              hover:bg-darkGreen hover:-translate-y-1 animate active:translate-y-0 active:bg-rose"
            >
              Login
            </button>
          </form>
          <button
            className="block text-center w-full mt-6 font-bold text-lightGreen text-sm"
            onClick={() => {
              const card = document.getElementById('pricing_cards')
              card.scrollIntoView({behavior: 'smooth'})
            }}
          >
            Don&apos;t have an account? Sign up
          </button>
          <button
            className="block text-center w-full mt-3 text-lightGreen text-sm"
            onClick={() => setAuthView('forgotten_password')}
          >
            Forgot my password
          </button>
          <button
            className="block text-center w-full mb-4 mt-3 text-lightGreen text-sm"
            onClick={() => setAuthView('magic_link')}
          >
            Login with a magic link
          </button>
        </>
      ) : authView === 'sign_up' ? (
        <>
          <h3 className="py-6 text-2xl text-center font-semibold uppercase">
            Create Your Free Account
          </h3>
          <form onSubmit={e => handleSignUp(e)}>
            <label
              htmlFor="sign-up__email"
              className="block font-semibold text-black"
            >
              Email
            </label>
            <input
              id="sign-up__email"
              name="Email address"
              autoComplete="email"
              placeholder="Type in your email address"
              defaultValue={email}
              className="text-input pl-4 ring-0 outline-none border"
              onChange={e => setEmail(e.target.value)}
            />
            <label
              htmlFor="sign-up__password"
              className="block font-semibold text-black mt-4"
            >
              Password
            </label>
            <input
              id="sign-up__password"
              name="Password"
              type="password"
              defaultValue={password}
              autoComplete="current-password"
              className="text-input pl-4 ring-0 outline-none border"
              onChange={e => setPassword(e.target.value)}
            />
            <button
              type="submit"
              className="py-4 my-6  w-full font-semibold flex place-content-center space-x-4
              text-white rounded-lg duration-300 transform text-md bg-lightGreen
              hover:bg-darkGreen hover:-translate-y-1 animate active:translate-y-0 active:bg-rose"
            >
              SignUp
            </button>
          </form>
        </>
      ) : authView === 'forgotten_password' ? (
        <>
          <h4 className="py-6 text-2xl text-center font-semibold uppercase">
            Update Password
          </h4>
          <form onSubmit={handlePasswordReset}>
            <label
              htmlFor="forgotten_password__email"
              className="block font-semibold text-black mt-3"
            >
              Email
            </label>
            <input
              id="forgotten_password__email"
              name="Email address"
              autoComplete="email"
              className="text-input pl-4 ring-0 outline-none border"
              placeholder="Type in your email address"
              defaultValue={email}
              onChange={e => setEmail(e.target.value)}
            />
            <button
              type="submit"
              className="py-4 mt-6 w-full font-semibold flex place-content-center space-x-4
              text-white rounded-lg duration-300 transform text-md bg-lightGreen
              hover:bg-darkGreen hover:-translate-y-1 animate active:translate-y-0 active:bg-rose"
            >
              Send reset password instructions
            </button>
          </form>
          <button
            className="block text-center w-full mt-2 mb-2 text-lightGreen text-sm"
            onClick={() => setAuthView('sign_in')}
          >
            Already have an account, login
          </button>
          <button
            className="block text-center w-full mt-2 mb-2 text-lightGreen text-sm"
            onClick={() => setAuthView('magic_link')}
          >
            Send magic link email
          </button>
        </>
      ) : authView === 'magic_link' ? (
        <>
          <h4 className="py-6 text-2xl text-center font-semibold uppercase">
            Magic link
          </h4>
          <form onSubmit={handleMagicLinkSignIn}>
            <label
              htmlFor="magic_link__email"
              className="block font-semibold text-black"
            >
              Email
            </label>
            <input
              id="magic_link__email"
              name="Email address"
              autoComplete="email"
              placeholder="Type in your email address"
              defaultValue={email}
              className="text-input pl-4 ring-0 outline-none border"
              onChange={e => setEmail(e.target.value)}
            />
            <button
              type="submit"
              className="py-4 mt-6 w-full font-semibold flex place-content-center space-x-4
              text-white rounded-lg duration-300 transform text-md bg-lightGreen
              hover:bg-darkGreen hover:-translate-y-1 animate active:translate-y-0 active:bg-rose"
            >
              Send magic link
            </button>
          </form>
          <button
            className="block text-center w-full mt-6 mb-2 text-lightGreen text-sm"
            onClick={() => setAuthView('sign_up')}
          >
            Don&apos;t have an account? Sign up
          </button>
          <button
            className="block text-center w-full mt-2 mb-2 text-lightGreen text-sm"
            onClick={() => setAuthView('sign_in')}
          >
            Already have an account, login
          </button>
        </>
      ) : null}
    </>
  )
}

function UpdatePassword({supabaseClient}) {
  const [password, setPassword] = useState('')
  const [error, setError] = useState('')
  const [message, setMessage] = useState('')
  const [loading, setLoading] = useState(false)
  const router = useRouter()

  const handlePasswordReset = async e => {
    e.preventDefault()
    setError('')
    setMessage('')
    setLoading(true)
    const {error} = await supabaseClient.auth.update({password})
    if (error) setError(error.message)
    else setMessage('Your password has been updated')
    setLoading(false)
    router.push('/hub')
  }

  return (
    <>
      {loading && <h3>Loading..</h3>}
      {error && <div style={{color: 'red'}}>{error}</div>}
      {message && <div style={{color: 'green'}}>{message}</div>}
      <h4 className="py-6 text-2xl text-center font-semibold uppercase">
        Set a new password
      </h4>
      <form onSubmit={handlePasswordReset}>
        <input
          name="New password"
          placeholder="Enter your new password"
          type="password"
          className="text-input pl-4 ring-0 outline-none border"
          onChange={e => setPassword(e.target.value)}
        />
        <button
          className="py-4 mt-6 w-full font-semibold flex place-content-center space-x-4
              text-white rounded-lg duration-300 transform text-md bg-lightGreen
              hover:bg-darkGreen hover:-translate-y-1 animate active:translate-y-0 active:bg-rose"
          type="submit"
        >
          Update password
        </button>
      </form>
    </>
  )
}

Auth.UpdatePassword = UpdatePassword
export default Auth

_app.js

import {UserContextProvider} from 'context/UserContext'
import {supabase} from 'utils/initSupabase'

function MyApp({Component, pageProps}) {
  return (
    <UserContextProvider supabaseClient={supabase}>
        <Component {...pageProps} />
    </UserContextProvider>
  )
}

export default MyApp

@syno3
Copy link

syno3 commented Oct 13, 2022

thanks, the workaround i end up doing was using supabase.auth.onAuthStateChange

// logout.js
await supabase.auth.signOut() // <= this trigger onAuthStateChange
const { data: authListener } = supabase.auth.onAuthStateChange(
      async (event, session) => {
        const body = JSON.stringify({ event, session })
        const headers = new Headers({ 'Content-Type': 'application/json' })

        await fetch('/api/login', {
          method: 'post',
          body,
          headers,
          credentials: 'same-origin',
        })
      }
    )
    return () => {
      authListener.unsubscribe()
    }

this way the cookie gets deleted

This is the only version that worked for me, sending an empty session when user logout unsets the cookie

@lauridskern
Copy link

Does anyone have a working solution for supabase 2.x? The problem still exists but the setAuthCookie got removed

@heikopaiko
Copy link

Not working with supabase 2.x any ideas or do we need to open a new issue?

@silentworks
Copy link
Contributor

@lauridskern @heikopaiko can you create a minimal reproducible example repository of this so I can check out what is going wrong as I have been unable to replicate this issue.

@vloe
Copy link

vloe commented Nov 13, 2022

signOut does still not delete cookie with nextjs ssr.

@heikopaiko
Copy link

@lauridskern @heikopaiko can you create a minimal reproducible example repository of this so I can check out what is going wrong as I have been unable to replicate this issue.

Here you go, based on the nuxt getting startet guide: nuxt-supabase-test

The "bug" is reproducible if you login via email and click the "signout button" WITHOUT refreshing the page beforehand.

@vloe
Copy link

vloe commented Nov 14, 2022

Finally figured it out! If anyone struggling with the same thing, this is how I got it working:

Don`t use the supabase client you first initialized, use the supabase client from useSupabaseClient.

In order for useSupabaseClient to work, you'll need your _app.tsx to look like this:

//_app.tsx
import '../styles/globals.css'
import type { AppProps } from 'next/app'
import { createBrowserSupabaseClient } from '@supabase/auth-helpers-nextjs'
import { SessionContextProvider, Session } from '@supabase/auth-helpers-react'
import { useState } from 'react'

export default function App({ Component, pageProps }: AppProps<{
  initialSession: Session
}>) {

  const [supabaseClient] = useState(() => createBrowserSupabaseClient())

  return (
    <SessionContextProvider
      supabaseClient={supabaseClient}
      initialSession={pageProps.initialSession}
    >
      <Component {...pageProps} />
    </SessionContextProvider>
  )
}

So, whenever you use signOut, signUpWithEmail, signInWithPassword etc... --> use this supabase client instead:

import { useSupabaseClient } from "@supabase/auth-helpers-react"

const supabaseClient = useSupabaseClient();

Here's a signOut example:

import { useSupabaseClient } from "@supabase/auth-helpers-react"

export default function Home() {

  const supabaseClient = useSupabaseClient()

  return (
    <div>
        <button onClick={async () => {
              await supabaseClient.auth.signOut();
          }}>
              click here to log out
        </button>
    </div>
)

@pradelkai
Copy link

const supabaseClient = useSupabaseClient();

I can confirm this solution works.

@bayramorhan
Copy link

bayramorhan commented Jan 16, 2023

Long story short, if you are having similar issue, (this solved in my case: Nuxt 3 SSR). Use useSupabaseAuthClient() instead of useSupabaseClient()

Working:

const supabase = useSupabaseAuthClient();
supabase.auth.signOut();

Not Working:

const supabase = useSupabaseClient();
supabase.auth.signOut();

@jacobcoro
Copy link

This is still a massive problem in our app, because we are using SWR for fetch requests, there are always a lot of open fetch requests in-flight when the signOut is called. The cookies are deleted by signOut, but immediately re-set when the in-flight requests resolve. I wish there was a way to invalidate the cookie server-side but it appears not.

@jacobcoro
Copy link

So the way I got around the 'inflight requests are resetting cookie' issue is:
window.location.href= "/logout" Then inside an empty pages/logout.tsx page, I call the auth.signOut function again and then finally redirect back to "/login" or "/signup"

The reason this works is that window.location.href will cancel in-flight requests, whereas router.push() in next.js will not.

Hope this helps someone. I was stuck for a while! I also had to add an abort controller on one fetch request that I couldn't stop because its called form my top level _app.tsx

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests