Skip to content

Bug: authentication persistence incompatible with React-native v0.71.x ( and Expo SDK 48 or upper) #7129

Closed
@Neosoulink

Description

@Neosoulink

[REQUIRED] Describe your environment

  • Operating System version: Node.js v18.15.0
  • Browser version: React-native v0.71.3 | Expo SDK 48
  • Firebase SDK version: Firebase v9.17.2
  • Firebase Product: auth

[REQUIRED] Describe the problem

It was working fine before, but after I updated Expo SDK from 46.0.0 to 48.0.0 and react-native from 0.69.6 to 0.71.3, the authentication persistence wasn't working anymore. I was required to log in every time I opened the app to connect.

I tried to force persistence by using reactNativeLocalPersistence but was returning an undefined, only inMemoryPersistence returned a value

So I realized that, since react-native v0.71.x Async-storage was removed from react-native and actually, firebase is using that Async-Storage from react-native to persist authentications

Steps to reproduce:

By using the following environment, you should get the same behavior:

  • Node.js v18.15.0
  • React-native v0.71.3 ( and Expo SDK 48 if you're using expo)
  • Firebase v9.17.2

Then try to use the authentication system, to log in with Email and password, for example, you'll notice the auth persistence will not work. The current User will be lost when the app activity will be destroyed.

Relevant Code:

Here is the class I used to manage my firebase logic

class FirebaseHelper {
	private app: FirebaseApp;

	constructor() {
		this.init();
	}

	/**
	 * Init firebase app
	 *
	 * @returns void
	 */
	init = () => {
		if (!this.app) {
			this.app = initializeApp({
				apiKey: ENV.FIREBASE_CONFIG.apiKey,
				authDomain: ENV.FIREBASE_CONFIG.authDomain,
				projectId: ENV.FIREBASE_CONFIG.projectId,
				storageBucket: ENV.FIREBASE_CONFIG.storageBucket,
				messagingSenderId: ENV.FIREBASE_CONFIG.messagingSenderId,
				appId: ENV.FIREBASE_CONFIG.appId,
				measurementId: ENV.FIREBASE_CONFIG.measurementId,
			});
		}
	};

	/**
	 * Method to get the Firebase App
	 */
	getApp = () => {
		return this.app;
	};
	
	/**
	 * Sign-up user and sign-in user
	 *
	 * @param form `SignUpFormType` object that contains new user data
	 */
	async signUp(form: SignUpFormType): Promise<boolean> {
		const _VALIDATION_CONSTRAINT = {
			username: {
				...REQUIRE_NOT_EMPTY_PRESENCE,
				length: {
					maximum: 15,
				},
			},
			email: REQUIRE_EMAIL,
			password: {
				...REQUIRE_NOT_EMPTY_PRESENCE,
				length: {
					minimum: 6,
				},
			},
			amount: REQUIRE_NUMERIC,
			confirmPassword: {
				equality: 'password',
			},
		};

		const _VALIDATION_RESULT = validate(form, _VALIDATION_CONSTRAINT);

		if (_VALIDATION_RESULT) {
			const _ERR_KEYS: string[] = [];
			Object.keys(_VALIDATION_RESULT).map((key) => _ERR_KEYS.push(key));

			showMessage({
				type: 'danger',
				message: _ERR_KEYS[0],
				description: _VALIDATION_RESULT[_ERR_KEYS[0]][0],
			});

			return false;
		}

		try {
			const _USER_CREDENTIALS = await createUserWithEmailAndPassword(
				this.auth,
				form.email,
				form.password,
			);

			if (_USER_CREDENTIALS.user) {
				const DEFAULT_PERIOD = SUPPORTED_PERIODS[0];
				const _USER_DOC_REF = doc(this.db, 'users', _USER_CREDENTIALS.user.uid);

				const _USER_GOAL_DOC_REF = doc(
					this.db,
					'users_goals',
					_USER_CREDENTIALS.user.uid,
				);

				const _NEW_USER_DATA: NewUserDataInterface = {
					email: form.email,
					name: form.username,
					photoURL: _USER_CREDENTIALS.user.photoURL || '',
					phoneNumber: _USER_CREDENTIALS.user.phoneNumber || '',
					signUpMethod: 'EmailAndPassword',
					deleted: false,
					createdAt: Timestamp.now(),
					updatedAt: Timestamp.now(),
				};

				const _NEW_USER_GOAL_DATA: NewUserGoalDataInterface = {
					amount: 0,
					amount_goal: parseInt(form.amount, 10),
					...DEFAULT_PERIOD,
					user: _USER_DOC_REF,
					startAt: Timestamp.now(),
					createdAt: Timestamp.now(),
					updatedAt: Timestamp.now(),
				};

				await setDoc(_USER_DOC_REF, _NEW_USER_DATA);
				await setDoc(_USER_GOAL_DOC_REF, _NEW_USER_GOAL_DATA);
				await this.setUpUserCharity({
					user: _USER_DOC_REF,
				});

				showMessage({
				        type: 'success',
				 	message: '🎉 Sign up successfully',
				});

				return true;
			}
		} catch (err) {
			console.warn('🚧 FirebaseHelper->signUp->catch', err);
			showMessage({
				type: 'danger',
				message: 'Something went wrong',
			});
		}

		return false;
	}

	/**
	 * Sign-in user
	 *
	 * @param form
	 */
	async signIn(
		form: SignInFormType,
		showSuccessMessage: boolean = true,
	): Promise<boolean> {
		try {
			const _USER_CREDENTIAL = await signInWithEmailAndPassword(
				this.auth,
				form.email,
				form.password,
			);

			if (_USER_CREDENTIAL.user) {
				if (await this.userDataExist(_USER_CREDENTIAL.user.uid)) {
					showSuccessMessage &&
						showMessage({
							type: 'success',
							message: '🎉 Connected successfully',
						});
					return true;
				}
				showMessage({
					type: 'danger',
					message: "🤔 It looks like you don't exist in our system",
					duration: 3500,
				});
			}
		} catch (err) {
			console.warn('🚧 FirebaseHelper->signIn->catch', err);
		}

		return false;
	}

       // Other methods ...
       
       	/**
	 * Getter that simply returns a Firestore instance
	 * @returns {Firestore}
	 */
	get db(): Firestore {
		return getFirestore(this.app);
	}

	/**
	 * Get an Auth instance
	 *
	 * @returns {Auth}
	 */
	get auth(): Auth {
		return getAuth(this.app);
	}
}

📌 Note
Actually, I just downgraded my react-native version to 0.70.5 and my expo SDK to 47 to make things work as expected

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions