supabase / supabase

The open source Firebase alternative.

Home Page:https://supabase.com

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Expo - React Native - Auth session missing!

thiagoadsix opened this issue · comments

Bug report

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

Describe the bug

I'm trying to reset a password for a user, but every time I attempt to update the password, Supabase returns an error message saying 'Auth session missing!', even though I have already set a new session

To Reproduce

  • I requested to reset my password.
  • I received the email in my inbox.
  • I clicked on the link.
  • I was redirected to the app.
  • I set the new password.
  • I received the error message.

Expected behavior

Once the user is inside the app, he should be able to reset the password.

Screenshots

Here is the print showing the email:
Simulator Screenshot - iPhone 15 - 2024-05-07 at 01 29 36

Here is the moment after I clicked on the link:
Simulator Screenshot - iPhone 15 - 2024-05-07 at 01 29 42

Here is the moment after I clicked on open:
Simulator Screenshot - iPhone 15 - 2024-05-07 at 01 29 48

More informations

Here is my Supabase Context:

import { GoogleSignin } from "@react-native-google-signin/google-signin";
import { Session, User } from "@supabase/supabase-js";
import * as Linking from "expo-linking";
import { usePathname, useRouter, useSegments } from "expo-router";
import React, { createContext, useEffect, useState } from "react";
import { Alert } from "react-native";

import { supabase } from "@/config/supabase";

type SignUpParams = {
  email: string;
  password: string;
  name: string;
  redirectTo: string;
};

type ResetPasswordParams = {
  email: string;
  redirectTo: string;
};

type SupabaseContextProps = {
  user: User | null;
  session: Session | null;
  initialized?: boolean;
  updatePassword: (password: string) => Promise<void>;
  requestResetPassword: ({
    email,
    redirectTo,
  }: ResetPasswordParams) => Promise<void>;
};

type SupabaseProviderProps = {
  children: React.ReactNode;
};

GoogleSignin.configure({
  scopes: ["https://www.googleapis.com/auth/drive.readonly"],
  webClientId: "...",
  iosClientId: "...",
});

const parseSupabaseUrl = (url: string) => {
  let parsedUrl = url;
  if (url.includes("#")) {
    parsedUrl = url.replace("#", "?");
  }

  return parsedUrl;
};

export const SupabaseContext = createContext<SupabaseContextProps>({
  user: null,
  session: null,
  initialized: false,
  updatePassword: async () => {},
  requestResetPassword: async () => {},
});

export const SupabaseProvider = ({ children }: SupabaseProviderProps) => {
  const [user, setUser] = useState<User | null>(null);
  const [session, setSession] = useState<Session | null>(null);
  const [initialized, setInitialized] = useState<boolean>(false);
  const pathname = usePathname();

  const segments = useSegments()[0];
  const router = useRouter();

  const updatePassword = async (password: string) => {
    console.log({ password });
    const { data, error } = await supabase.auth.updateUser({
      password,
    });

    if (error) {
      console.error(error);
      throw error;
    }

    console.log({ data });
  };

  const requestResetPassword = async ({
    email,
    redirectTo,
  }: ResetPasswordParams) => {
    const { error } = await supabase.auth.resetPasswordForEmail(email, {
      redirectTo,
    });

    if (error) {
      throw error;
    }
  };

  useEffect(() => {
    supabase
      .channel("schema-db-changes")
      .on(
        "postgres_changes",
        {
          event: "UPDATE",
          schema: "public",
          table: "roles",
        },
        (payload) => supabase.auth.refreshSession()
      )
      .subscribe();

    supabase
      .channel("schema-db-changes")
      .on(
        "postgres_changes",
        {
          event: "INSERT",
          schema: "public",
          table: "roles",
        },
        (payload) => supabase.auth.refreshSession()
      )
      .subscribe();
  }, []);

  useEffect(() => {
    const { data } = supabase.auth.onAuthStateChange(async (event, session) => {
      setSession(session);
      setUser(session ? session.user : null);
      setInitialized(true);
    });

    return () => {
      data.subscription.unsubscribe();
    };
  }, []);

  useEffect(() => {
    (async () => {
      const url = await Linking.getInitialURL();
      if (url?.includes("reset-password")) {
        const transformedUrl = parseSupabaseUrl(url);
        const parsedUrl = Linking.parse(transformedUrl);

        const accessToken = parsedUrl.queryParams?.access_token;
        const refreshToken = parsedUrl.queryParams?.refresh_token;

        if (
          typeof accessToken === "string" &&
          typeof refreshToken === "string"
        ) {
          try {
            const { data, error } = await supabase.auth.setSession({
              access_token: accessToken,
              refresh_token: refreshToken,
            });

            await supabase.auth.refreshSession();

            if (error) throw error;

            setSession(data.session);
            setUser(session ? session.user : null);

            if (data.session) {
              router.replace("/(app)/settings/reset-password/");
            }
          } catch (error) {
            console.error("Failed to set session:", error);
          }
        }
      }
    })();
  }, []);

  useEffect(() => {
    if (!initialized) return;

    if (!session && pathname !== "/(auth)/welcome") {
      router.replace("/(auth)/welcome");
    } else if (session && !pathname.includes("/settings/reset-password")) {
      router.replace("/(app)/home/");
    }
  }, [initialized, session, segments, router]);

  return (
    <SupabaseContext.Provider
      value={{
        user,
        session,
        initialized,
        updatePassword,
        requestResetPassword,
      }}
    >
      {children}
    </SupabaseContext.Provider>
  );
};

System information

  • OS: [e.g. macOS]
  • Version of @supabase/supabase-js: [e.g. 2.38.0]
  • Version of Node.js: [e.g. 20.10.0]

Maybe I'm missing something about states, react component mounting, or something else that I don't have any idea.

I'm trying to update the session from user on this useEffect, but for some reason, this is not updating the session, because Supabase keeps saying: Auth session missing!:

  useEffect(() => {
    (async () => {
      const url = await Linking.getInitialURL();
      if (url?.includes("reset-password")) {
        const transformedUrl = parseSupabaseUrl(url);
        const parsedUrl = Linking.parse(transformedUrl);

        const accessToken = parsedUrl.queryParams?.access_token;
        const refreshToken = parsedUrl.queryParams?.refresh_token;

        if (
          typeof accessToken === "string" &&
          typeof refreshToken === "string"
        ) {
          try {
            const { data, error } = await supabase.auth.setSession({
              access_token: accessToken,
              refresh_token: refreshToken,
            });

            await supabase.auth.refreshSession();

            if (error) throw error;

            setSession(data.session);
            setUser(session ? session.user : null);

            if (data.session) {
              router.replace("/(app)/settings/reset-password/");
            }
          } catch (error) {
            console.error("Failed to set session:", error);
          }
        }
      }
    })();
  }, []);