-
-
Notifications
You must be signed in to change notification settings - Fork 159
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
Comments
Thanks @gomflo - I'm shifting this to gotrue-js so we can solve it asap 👍 |
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 |
From a cursory look at our implementation, we have a listener on sign out: So we should verify the reason why it isn't working here. I'm guessing this can be replicated in our Nextjs example |
@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. |
I have a page named And from any page i have the following tag to logout: <a href="/logout">Logout</a> Thats my const onSubmit = async ({ email }) => {
const { error } = await supabase.auth.signIn({ email })
} Then when te user click the magic link i have the following page 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 import { supabase } from '@/utils/initSupabase'
export default function handler(req, res) {
supabase.auth.api.setAuthCookie(req, res)
} |
@gomflo Thanks for posted example! I tried it and it failed for me as well. Any guesses @kiwicopple? |
maybe related to #73 |
Hi @phamhieu, currently I am using version ^1.11.6 |
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 |
After checking the code on
on next-js example, it uses // 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 @Ali-Parandeh On your signOut api, you didn't remove the cookie. May i how do you set the cookie on signIn? |
In next-js example we use |
@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. |
@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... |
Here is my solution: 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
}
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}
import {supabase} from 'utils/initSupabase'
export default function handler(req, res) {
supabase.auth.api.setAuthCookie(req, res)
}
// 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)
}
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'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'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
import {UserContextProvider} from 'context/UserContext'
import {supabase} from 'utils/initSupabase'
function MyApp({Component, pageProps}) {
return (
<UserContextProvider supabaseClient={supabase}>
<Component {...pageProps} />
</UserContextProvider>
)
}
export default MyApp |
This is the only version that worked for me, sending an empty session when user logout unsets the cookie |
Does anyone have a working solution for supabase 2.x? The problem still exists but the setAuthCookie got removed |
Not working with supabase 2.x any ideas or do we need to open a new issue? |
@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. |
signOut does still not delete cookie with nextjs ssr. |
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. |
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>
) |
I can confirm this solution works. |
Long story short, if you are having similar issue, (this solved in my case: Nuxt 3 SSR). Use useSupabaseAuthClient() instead of useSupabaseClient() Working:
Not Working:
|
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 |
So the way I got around the 'inflight requests are resetting cookie' issue is: The reason this works is that 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 |
Bug report
Describe the bug
supabase.auth.signOut() is not deleting the cookie created by setAuthCookie
The text was updated successfully, but these errors were encountered: