auth-ui icon indicating copy to clipboard operation
auth-ui copied to clipboard

Password reset is not working

Open renardeinside opened this issue 2 years ago • 49 comments

Bug report

I'm trying to set up password reset flow in my nextjs app with supabase and supabase auth ui. It's not working and I'm struggling to understand the root cause.

Describe the bug

When a reset link is clicked, no password reset dialog is shown.

To Reproduce

Steps to reproduce the behavior, please provide code snippets or a repository:

  1. Instantiate a nextjs app with supabase and auth-ui
  2. Create a new page with the following content:
// LOCATED IN pages/auth-ui.jsx
import {
    Auth,
    // Import predefined theme
    ThemeSupa,
} from '@supabase/auth-ui-react'
import {useSupabaseClient} from "@supabase/auth-helpers-react";
import Container from "@mui/material/Container";

const AuthUi = () => {
    const supabase = useSupabaseClient();

    return(
        <Container maxWidth={"sm"}>
            <Auth
                supabaseClient={supabase}
                appearance={{ theme: ThemeSupa }}
                theme="dark"
                redirectTo={`http://app.local/auth-ui`}
            />
        </Container>

    )
}

export default AuthUi
  1. Setup your auth settings in supabase:
Site URL: http://app.local
Redirect Url: http://app.local

Side note - I'm using http here since I have a local instance of my app on this redirect URL, so the certificate is not a concern.

  1. Using the AuthUI, generate an email for a password reset. The link in the email will be in the format of:
https://XXX.supabase.co/auth/v1/verify?token=XXX&type=recovery&redirect_to=http://app.local/auth-ui
  1. Click the link
  2. Auth flow will end up with the following path:
http:/app.local/auth-ui#

The user will remain signed in, but no reset password view will be shown.

Expected behavior

It works and doesn't require users to debug this - it's such a basic functionality, strange that it's not working correctly by default.

Screenshots

If applicable, add screenshots to help explain your problem.

System information

  • OS: [e.g. macOS, Windows] macos
  • Browser (if applies) [e.g. chrome, safari] chrome
  • Version of supabase-js: [e.g. 6.0.2]:
    "@supabase/auth-helpers-nextjs": "^0.5.2",
    "@supabase/auth-helpers-react": "^0.3.1",
    "@supabase/auth-ui-react": "^0.2.6",
    "@supabase/supabase-js": "^2.1.0",
  • Version of Node.js: [e.g. 10.10.0]:
> node -v    
v16.7.0

Additional context

If you'll click the link second time, you'll not get a proper error handle, instead, the redirect will look like this:

http://xxx.local/auth-ui#error=unauthorized_client&error_code=401&error_description=Email+link+is+invalid+or+has+expired

It's quite clear that it should look like this (query, not an anchor):

http://xxx.local/auth-ui?error=unauthorized_client&error_code=401&error_description=Email+link+is+invalid+or+has+expired

renardeinside avatar Nov 26 '22 12:11 renardeinside

Possible duplicate of https://github.com/supabase/auth-ui/issues/72

0x111 avatar Nov 28 '22 20:11 0x111

Seems like issue #349 in the gotrue-js repo is related.

In the past, with the old Auth component from the now-deprecated Supabase UI library and v1 of the client lib, I had to listen to the changes in auth state manually in my auth provider, and set the UI view accordingly:

useEffect(() => {
  const activeSession = supabase.auth.session();
  setSession(activeSession);
  setUser(activeSession?.user ?? null);

  const { data: authListener } = supabase.auth.onAuthStateChange(
    (event, currentSession) => {
      // ...
      switch (event) {
          case EVENTS.PASSWORD_RECOVERY:
            setView(VIEWS.UPDATE_PASSWORD);
            break;
          case EVENTS.SIGNED_OUT:
          case EVENTS.USER_UPDATED:
            setView(VIEWS.SIGN_IN);
            break;
          default:
        }
    }
  );

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

Then manually show the "Reset Password" form, as it seemed to be the only way to get this to work:

if (view === VIEWS.UPDATE_PASSWORD) {
  return (
    <Auth.UpdatePassword supabaseClient={supabase} />
  );
}

return (
  <Layout>
    {user && (
      {/** Logged-in state */}
    )}
    {!user && <Auth view={view} supabaseClient={supabase} />}
  </Layout>
);

But I'm now discovering this doesn't work anymore for some reason. The PASSWORD_RECOVERY event never seems to fire after going to the URL in the Reset Password email.

Even with the old code and library versions, it doesn't seem to work anymore. I wrote a full post on doing this with the old library, and discovered this issue while updating it for v2 and the new Auth UI.

Seems like the issue is present in both v1 and v2. I found more related issues on this as well:

https://github.com/supabase/gotrue/issues/960 https://github.com/supabase/gotrue-js/issues/305

And this comment goes into more detail on the reset password flow.

EDIT: Found another comment that may shed some light on this behaviour.

The TL;DR is that it seems like the Auth UI still doesn't automatically handle what should be a pretty straightforward auth flow.

mryechkin avatar Dec 08 '22 19:12 mryechkin

The provided Auth UI code just does not work whatsoever. It did not mention @supabase/auth-helpers-react. If you cannot provide any working example, why bother to release this?

jt6677 avatar Dec 10 '22 13:12 jt6677

@jt6677 this repository has no relation to auth-helpers-react, they are two independent projects which can be used together if the developer wishes to. There are many examples inside of storybook in this repo. Also there are projects using the Auth UI, so I would think its working, it would be useful if you provided more concrete example of what is not working, even better if you provide a minimal reproducible repository with the issue you are having.

silentworks avatar Dec 12 '22 00:12 silentworks

@renardeinside please provide a repo with the issue you are having, since you are using NextJS its likely that you are getting redirected to the wrong page because the server loads before the client and you get redirect by the server as when it checks to see if you are logged in you would not be at the time the server side page renders.

silentworks avatar Dec 12 '22 00:12 silentworks

im having this problem too. After i click 'reset password' link in email, i just get taken to my regular login page.

Jared-Dahlke avatar Dec 18 '22 21:12 Jared-Dahlke

Im having this problem too. https://discordapp.com/channels/839993398554656828/1068736076140773466/1068736076140773466

hiroshinishio avatar Feb 01 '23 15:02 hiroshinishio

I didn't try it properly yet, but I can se that in the Solid.js version of the Auth component there's an event listener that handles the redirect you're talking about.

  createEffect(() => {
    /**
     * Overrides the authview if it is changed externally
     */
    const { data: authListener } =
      mergedProps().supabaseClient.auth.onAuthStateChange((event, session) => {
        if (event === 'PASSWORD_RECOVERY') {
          setAuthView(VIEWS.UPDATE_PASSWORD)
        } else if (event === 'USER_UPDATED') {
          setAuthView(VIEWS.SIGN_IN)
        }
      })
    setAuthView(mergedProps().view)

    onCleanup(() => authListener.subscription.unsubscribe())
  })

In the React.js version it's missing

  useEffect(() => {
    /**
     * Overrides the authview if it is changed externally
     */
    setAuthView(view)
  }, [view])

I don't know the project, so it might be that I'm missing something here, but to me it looks like those two bits of code should be the same.

giovanniRodighiero avatar Feb 01 '23 20:02 giovanniRodighiero

I've created a very minimal example here: https://github.com/chrisk-7777/supabase-reset-test

It does "work", but it doesn't feel entirely intuitive or documented. Unless I'm missing something in the docs. I've read this dicusssion a half dozen times to really "get" what is going on.

Disclaimer, I'm new to Supabase and its ecosystem, but not new to authentication.

There were two key points that I initially missed:

  1. Auth UI includes a "forget password" view, but not a "reset password" view. To me this was confusing. The confusion comes from my experience with other auth solutions like Auth0 and Laravel's Sanctum/Fortify/Breeze - where a reset flow isn't also a magic link / sign in combo. I can't recall which one Firebase uses, I'll check later. I noticed this recent PR, which is off the back of this confusion. But if anything, that makes it more confusing because someone (like myself) may conflate that to mean that "forgot" is "reset". If the team is open to it, I would like to raise a PR saying that "reset password" view is excluded. The docs kind of hint to the user being signed in here but it doesn't actually mention a session is started, just that a sign in event occurred. Maybe that is obvious to others, but reading around on various issues/discussions its not.

  2. Within the React version of Auth UI, it never reaches the update_password by itself (correct me if I'm wrong). So for the particular case of a post-forget-magic-link-login PASSWORD_RECOVERY event, we need to set some app level state, and manually set the view in consumer land. In hindsight, I guess this makes sense, it makes it handy to have that view available for general password updates, but its confusing that view is sometimes handled internally (swapping between login, register and forgot views), and sometimes handled externally (update password view). Maybe its just me?

Bonus: One other thing that completely caught me off guard was onAuthStateChange should be unregistered, like this, right? Again, I'm new to Supabase, but without unsubscribing, a component re-render will re-fire useEffect, and queue up multiple listeners - is that right? I bring it up because the documentation here outlines the subscribe occurring in a useEffect without any cleanup.

Separately, I believe there is some NextJS funkiness going on. The same thing that @mryechkin is experiencing (sorry to @ you, but I've seen you pop up in nearly every password reset discussion/issue across supabase's repo's, and have helped me understand what is going on). As mentioned somewhere else, the "PASSWORD_RECOVERY" event isn't fired, but is probably related to a clash with Next' routing / rendering modes. I'll try put together a minimal repo for that too.

None of this should be taken as criticism, I'm just trying to clarify my understanding after a day of going around in circles.

chrisk-7777 avatar Feb 08 '23 06:02 chrisk-7777

A minimal repo for NextJS: https://github.com/chrisk-7777/supabase-reset-next-test

I'm intentionally avoiding the convenience helpers to keep the surface area small.

If onAuthStateChange is subscribed to within a root page (like index.tsx), or a sub page (like reset.tsx) then everything works fine, just with the same notes from my previous comment.

Note: This is all SSG, no SSR.

chrisk-7777 avatar Feb 08 '23 08:02 chrisk-7777

@chrisk-7777 I ended up ditching the Auth UI library and just created my own Auth forms - much easier than trying to work around the gotchas in this library (see my comment here).

Auth UI includes a "forget password" view, but not a "reset password" view. To me this was confusing. ... But if anything, that makes it more confusing because someone (like myself) may conflate that to mean that "forgot" is "reset"

I wholeheartedly agree with this, and also find it quite strange that the "Forgot" form is included, but not the "Reset". In my mind the two go hand-in-hand.

So for the particular case of a post-forget-magic-link-login PASSWORD_RECOVERY event, we need to set some app level state, and manually set the view in consumer land. In hindsight, I guess this makes sense, it makes it handy to have that view available for general password updates, but its confusing that view is sometimes handled internally (swapping between login, register and forgot views), and sometimes handled externally (update password view). Maybe its just me?

Yup.. also agreed, and no it's not just you - I was also quite confused when I started looking into this initially. It's again the reason I opted to not use the provided UI library once I moved to Supabase v2, and created my own auth forms and the context provider.

mryechkin avatar Feb 08 '23 19:02 mryechkin

To everyone in this thread I really appreciate the time you've all taken to explain the difficulties you are facing with the library and the lack of documentation which has led to these difficulties. I am taking it all into consideration as we formulate how to best fix these issues. I think the first step is to document how you would go about handling password reset as this is something that keeps on coming up and it differs per environment [server-side generated (SSG) and single page application (SPA)]. There is also scenarios where it may even work different in SPA environments too because in the case of you using a router in your SPA like React Router you would use the component similar to how you would use it in a SSG environment. I've added in the event listener in the auth-ui-react library to match that of the auth-ui-solid version, but this in effect is only useful in an SPA without a router setup.

silentworks avatar Mar 03 '23 00:03 silentworks

I just want to come back and give an update here that this issue hasn't been forgotten. I'm currently working on some docs to explain how this flow is supposed to work and then will create some examples with the Auth UI to show how it works.

silentworks avatar Apr 30 '23 00:04 silentworks

Thanks @silentworks

Is it only changes in the docs that are needed? Or is it upstream in GoTrue? Or the Supabase wrappers? Aside from the two minimal repo's replicating the issue, is there a way to help this along?

chrisk-7777 avatar May 06 '23 04:05 chrisk-7777

@chrisk-7777 It's mainly a documentation issue. I've written the docs already but just waiting on a certain feature to rollout fully to all projects on the Supabase platform.

You can view the docs PR here https://github.com/supabase/supabase/pull/14111

Note that this doc isn't Auth UI specific but a general password reset doc, I will add examples of how you would do this with Auth UI in later on.

silentworks avatar May 07 '23 22:05 silentworks

Would it be possible to use the auth-ui-react Auth component and be able to use the Password Reset functionality in combination with Next.JS (SSR)? It seems that this is not supported, out of the box?

Bartel-C8 avatar Jun 22 '23 07:06 Bartel-C8

@Bartel-C8 looks like the docs were updated here: https://supabase.com/docs/guides/auth/auth-password-reset . This covers the SSR flow

With the merged PR that Silentworks mentioned above.

chrisk-7777 avatar Jun 22 '23 09:06 chrisk-7777

@chrisk-7777 , thank you.

But this should be handled/done already in the auth-ui-react package: https://github.com/supabase/auth-ui/blob/main/packages/react/src/components/Auth/interfaces/ForgottenPassword.tsx

But when using that (actually I am just using the todo template: https://github.com/supabase/supabase/tree/master/examples/todo-list/nextjs-todo-list), there is no way to see the Reset Password page somehow. In my case, clicking the reset password link, as received in the email, acts as a "magic link" just showing the page as a user was just logged in. I see no reset_password view unfortunately...

Could be related to: https://github.com/supabase/supabase/issues/14890#issuecomment-1585797609

Bartel-C8 avatar Jun 22 '23 09:06 Bartel-C8

@chrisk-7777 It's mainly a documentation issue. I've written the docs already but just waiting on a certain feature to rollout fully to all projects on the Supabase platform.

You can view the docs PR here supabase/supabase#14111

Note that this doc isn't Auth UI specific but a general password reset doc, I will add examples of how you would do this with Auth UI in later on.

Can you give an example of how you would do this with Auth UI? I can't figure it out. This is the basic pattern I was expecting to work:

const [session, setSession] = useState()

useEffect(() => {
  supabase.auth.getSession().then(({ data: { session } }) => setSession(session))
}, [])

if (!session) return <Auth supabaseClient={supabase} />
return <MyContent>

However, this won't work with the password recovery links that Auth UI sends, because the user gets logged in and then the Auth UI doesn't render. What else can I check to keep rendering Auth instead of MyContent, in order to update the password? It doesn't seem like I can check type=recovery in the query params because something in the auth code changes the URL.

levity avatar Jul 11 '23 20:07 levity

@chrisk-7777 It's mainly a documentation issue. I've written the docs already but just waiting on a certain feature to rollout fully to all projects on the Supabase platform. You can view the docs PR here supabase/supabase#14111 Note that this doc isn't Auth UI specific but a general password reset doc, I will add examples of how you would do this with Auth UI in later on.

Can you give an example of how you would do this with Auth UI? I can't figure it out. This is the basic pattern I was expecting to work:

const [session, setSession] = useState()

useEffect(() => {
  supabase.auth.getSession().then(({ data: { session } }) => setSession(session))
}, [])

if (!session) return <Auth supabaseClient={supabase} />
return <MyContent>

However, this won't work with the password recovery links that Auth UI sends, because the user gets logged in and then the Auth UI doesn't render. What else can I check to keep rendering Auth instead of MyContent, in order to update the password? It doesn't seem like I can check type=recovery in the query params because something in the auth code changes the URL.

I have same problem.

MichalBunkowski avatar Jul 12 '23 07:07 MichalBunkowski

@chrisk-7777 , thank you.

But this should be handled/done already in the auth-ui-react package: https://github.com/supabase/auth-ui/blob/main/packages/react/src/components/Auth/interfaces/ForgottenPassword.tsx

But when using that (actually I am just using the todo template: https://github.com/supabase/supabase/tree/master/examples/todo-list/nextjs-todo-list), there is no way to see the Reset Password page somehow. In my case, clicking the reset password link, as received in the email, acts as a "magic link" just showing the page as a user was just logged in. I see no reset_password view unfortunately...

Could be related to: supabase/supabase#14890 (comment)

I mentioned something similar above: https://github.com/supabase/auth-ui/issues/77#issuecomment-1422071763 (dot point 1). Your description of "magic link" is correct in my opinion.

This is also part of the updated docs in this PR (https://github.com/supabase/supabase/pull/14111/files). They even use the term "magic link". You will need to "manually" present some password UI (like an input box) and call the updateUser method.

Maybe resetPasswordForEmail is the wrong term to start this flow, but I can't think of a better one tbh.

chrisk-7777 avatar Jul 12 '23 10:07 chrisk-7777

Hey folks I can see that there are still confusion around how this process works. I think one of the issue here is that folks think the Auth UI is supposed to be a smart component when in-fact it's just a dumb component, it doesn't do much more than provide you with a UI and make the API call, the rest is up to you the developer to handle. When doing a password reset flow you need to provide the page that the update password should happen on, on that page you can use the Auth UI component. You can take a look at the examples I've created here https://github.com/supabase-community/supabase-by-example/tree/main/reset-flow/auth-ui, note that you might see new component names being introduced like ForgottenPassword these are just wrappers around the Auth component that set the view property to the correct state. These could have easily been the Auth component with view property set to forgotten_password. You can see this component here https://github.com/supabase/auth-ui/blob/main/packages/react/src/components/Auth/ui/index.tsx#L93-L105

silentworks avatar Jul 12 '23 10:07 silentworks

I agree regarding the confusion, its probably related to the typical "forget" flow being different (I made a few comparisons to other auth solutions in my comment above).

So it might be worth highlighting this difference in the docs?

Just a few bits like:

  • In the supported views section, link to the supabase reset flow
  • Rename "forgotten password" to "password reset" for consistent language
  • Highlight that the "forgotten password" / "password reset" view doesn't (yet) include an update password view. Simply to avoid further confusion, and possibly redirect them over to the auth-password-reset docs again.

chrisk-7777 avatar Jul 12 '23 10:07 chrisk-7777

@chrisk-7777 good suggestions. I will add them to my list of todos and get the docs update to match this. Do note that there is an update password view, it's just that you have to manually set the views with the Auth component.

You can see the individual component which just uses this view in the Auth component. https://github.com/supabase/auth-ui/blob/main/packages/react/src/components/Auth/ui/index.tsx#L107-L114

silentworks avatar Jul 12 '23 10:07 silentworks

Hey folks I can see that there are still confusion around how this process works. I think one of the issue here is that folks think the Auth UI is supposed to be a smart component when in-fact it's just a dumb component, it doesn't do much more than provide you with a UI and make the API call, the rest is up to you the developer to handle. When doing a password reset flow you need to provide the page that the update password should happen on, on that page you can use the Auth UI component. You can take a look at the examples I've created here https://github.com/supabase-community/supabase-by-example/tree/main/reset-flow/auth-ui, note that you might see new component names being introduced like ForgottenPassword these are just wrappers around the Auth component that set the view property to the correct state. These could have easily been the Auth component with view property set to forgotten_password. You can see this component here https://github.com/supabase/auth-ui/blob/main/packages/react/src/components/Auth/ui/index.tsx#L93-L105

I think the confusion arises because the Auth component is “half-smart”.

If you just drop it in, it lets you switch between all the different views, so it will already allow login, signup, and sending password reset links. Which is great! So it makes you think it Just Works.

But it doesn’t let you configure each of those views separately. You can’t customize redirectTo only for the password reset link. If we could do that, for example, this would be easy to fix.

So I think it’d be better either for it to be really dumb (not handle switching views with setAuthView, out-of-the-box), or smarter. Obviously I prefer the latter. :) Happy to help out with a PR if I hear some consensus about what to do.

levity avatar Jul 12 '23 16:07 levity

@levity this is exactly why I opted to just use my own auth forms instead of relying on what feels like a half-baked solution with Auth UI. It would be great it it handled all the things like you said - but the fact that it doesn't let us configure some things while also requiring to manually implement other things makes using this component an exercise in frustration, and not really worth the effort IMO.

mryechkin avatar Jul 13 '23 13:07 mryechkin

I made a PR that does what @levity describes and that works for me. You can see it here: #223 with my comment describing the desired reset password flow. But I must still be missing something regarding how to implement this flow without re-implementing a bunch of these components or even doing everything server-side. I would really appreciate pointers on how to get this working with the Auth components.

ozanmakes avatar Sep 25 '23 13:09 ozanmakes

I think where I'm most confused is that it presents itself as a fully baked component... There are no state change callback props. For example, if the "view" is changed in the component, and we are supposed to handle that separately, why is there no onViewChange callback? I see that maybe theres a hash added to the url but having a callback would be a lot more intuitive and clear about how the component can be handled - especially if we want to customize a specific view. There's no way for me to handle, for example, onSignup vs onSignin or some sort of auth change type callback from that view component so I can send the user to an additional form for sign up details IF they just signed up. I understand that can be done by listening to auth helpers but i just think some callbacks from the auth ui would make setup a lot easier and more intuitive as to how you can handle it.

TylerAHolden avatar Oct 11 '23 17:10 TylerAHolden

I'm pretty confused with how this feature is supposed to work. How do I distinguish between recovering a password and restoring the session in a client app? Using the React quickstart from the docs, the recovery link logs you in immediately and I don't see a simple way to fix it.

rsaksida avatar Oct 11 '23 18:10 rsaksida

Here to +1 to the fact that the Auth UI component is extremely misleading. Spent a decent chunk of time trying to use the component for my app, but instead kept uncovering limitations that make it a complete hassle to implement.

For those of you who happen to be reading this and are considering using Auth UI — don't! Implement your own or use another service instead of this one since it's half-baked as @mryechkin said.

The documentation should make it extremely clear that this is not a working solution. I went down this rabbit hole because the official documentation on Supabase's site made it look like it was an appealing solution

kev2010 avatar Oct 19 '23 02:10 kev2010