简体   繁体   中英

How to persist the Firebase Auth state between front-end and backend ? (Firebase Auth + React + Express)

I am trying to use Firebase Auth in backend, but I can't seem to be able to have the same Auth instance in the front-end as well.

The back-end:

'use strict';

import { firebaseAdmin, auth } from '../firebase.js';
import deleteCollection from '../helpers/deleteCollection.js';
import User from '../models/user.js';
import {
  createUserWithEmailAndPassword,
  updateProfile,
  signInWithEmailAndPassword,
  signOut,
  setPersistence,
  browserLocalPersistence,
} from 'firebase/auth';

const firestore = firebaseAdmin.firestore();

const register = async (req, res, next) => {
  try {
    // name, email, password
    const { name, email, password, avatar } = req.body;
    console.log('sent from frontend', { name, email, password });
    // Check if email or password were sent
    if (!email || !password) {
      return res.status(422).json({
        email: 'Email is required !',
        password: 'Password is required !',
      });
    }
    const usersCollection = firestore.collection('users');
    // Reference to a QuerySnapshot whith all users that have the requested name
    const userSnapshot = await usersCollection.where('name', '==', name).get();
    // Check if user already exists:
    if (!userSnapshot.empty) {
      throw new Error('Username is taken !');
    } else {
      await setPersistence(auth, browserLocalPersistence);
      // Firebase Auth Create User
      await createUserWithEmailAndPassword(auth, email, password);
      // User is signed in
      const user = auth.currentUser;
      if (user) {
        await updateProfile(user, {
          displayName: name,
        });
        const setUser = {
          id: user.uid,
          name: user.displayName,
          avatar: avatar,
        };
        await usersCollection.doc(setUser.id).set(setUser);
        res.status(201).send(setUser);
      } else {
        throw new Error('No user');
      }
    }
  } catch (error) {
    const errorCode = error.code;
    const errorMessage = error.message;

    res.status(400).send(errorMessage);
    console.log(errorCode, errorMessage);
  }
};

const login = async (req, res, next) => {
  try {
    const { email, password } = req.body;

    await setPersistence(auth, browserLocalPersistence);
    const userCred = await signInWithEmailAndPassword(auth, email, password);

    const usersCollection = firestore.collection('users');
    const userSnapshot = await usersCollection
      .where('name', '==', userCred.user.displayName)
      .get();
    if (userSnapshot.empty) {
      throw new Error('User does not exist !');
    } else {
      let user;

      userSnapshot.forEach((doc) => (user = { ...doc.data() }));
      res.status(200).send(user);
    }
  } catch (error) {
    res.status(404).send(error.message);
    console.log(error);
  }
};

const logout = async (req, res, next) => {
  try {
    // const { name, email, password, avatar } = req.body;

    await signOut(auth);
    res.sendStatus(200);
  } catch (error) {
    const errorCode = error.code;
    const errorMessage = error.message;

    res.status(404).send(errorMessage);
    console.log(error);
  }
};

I call Register, Login and Logout using Redux thunkAPI:

const register = async (userData) => {
  const response = await axios.post(API_REGISTER, userData, {
    headers: {
      // Overwrite Axios's automatically set Content-Type
      'Content-Type': 'application/json',
    },
  });

  if (response.data) {
    // localStorage.setItem('user', JSON.stringify(response.data));
  }
  return response.data;
};

const login = async (userData) => {
  const response = await axios.post(API_LOGIN, userData, {
    headers: {
      // Overwrite Axios's automatically set Content-Type
      'Content-Type': 'application/json',
    },
  });

  if (response.data) {
    // localStorage.setItem('user', JSON.stringify(response.data));
  }
  return response.data;
};

const logout = async () => {
  const response = await axios.get(`${API_LOGOUT}`);

  if (response.data) {
    localStorage.removeItem('user');
  }
  return response.data;
};
export const register = createAsyncThunk(
  'user/register',
  async (user, thunkAPI) => {
    try {
      return await userService.register(user);
    } catch (error) {
      return thunkAPI.rejectWithValue(error.response.data);
    }
  }
);

export const login = createAsyncThunk('user/login', async (user, thunkAPI) => {
  try {
    return await userService.login(user);
  } catch (error) {
    return thunkAPI.rejectWithValue(error.response.data);
  }
});

export const logout = createAsyncThunk('user/logout', async (_, thunkAPI) => {
  try {
    return await userService.logout();
  } catch (error) {
    return thunkAPI.rejectWithValue(error.response.data);
  }
});

I am able to Register a user, to login and to logout, but if I hit refresh I get logged out.

I am not able to persist the Firebase Auth state between front-end and backend.

This is the Private Route component

import { useSelector } from 'react-redux';
import { Navigate, useLocation } from 'react-router-dom';

import { auth } from '../../firebase';
import { useAuthState } from 'react-firebase-hooks/auth';
import { useEffect } from 'react';
import { useState } from 'react';

let isAuth;

export default function PrivateRoute({ children }) {
  const location = useLocation();
  const [user, setUser] = useState();
  // const isAuth = useSelector((state) => state.user.user);

  // const [user, loading, error] = useAuthState(auth);

  // useEffect(() => {
  //   if (loading) return;
  //   if (user) {
  //     isAuth = true;
  //     console.log(user);
  //   }
  // }, [user, loading]);

  useEffect(() => {
    auth.onAuthStateChanged(setUser);
  }, []);

  return user ? (
    children
  ) : (
    <Navigate
      replace={true}
      to='/login'
      state={{ from: `${location.pathname}${location.search}` }}
    />
  );
}

As you can see from the commented code, I've tried multiple things before posting here but nothing works.

I don't want to move the Authentication logic from back-end to front-end.

I only want to have access to the same Auth state between back-end to front-end.

The approach you're using is not supported by Firebase. You're supposed to authenticate the user only on the frontend and never on the backend. The frontend SDK will persist a token that identifies the user. You then pass that token to the backend on each call and use it to verify the user so that the backend can decide if the operation they are trying to perform is allowed. This scheme is described in the documentation , and I strongly suggest reviewing that:

If your Firebase client app communicates with a custom backend server, you might need to identify the currently signed-in user on that server. To do so securely, after a successful sign-in, send the user's ID token to your server using HTTPS. Then, on the server, verify the integrity and authenticity of the ID token and retrieve the uid from it. You can use the uid transmitted in this way to securely identify the currently signed-in user on your server.

Again, don't try to sign the user in on your backend using the frontend SDK - that is not supported and it does not scale. Only use the Firebase Admin SDK on the backend to validate the user ID tokens passed from the frontend.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM