I'm trying to implement authentication in a react app using the context API and Firebase's auth service and I'm halfway through, except for the fact that I'm not getting the desired results.
For example, if I click on the submit button without typing anything, it's supposed to throw an auth error of invalid email and show a post error message to firebase in the console. The same goes for if I type a password that's less than 6 characters. But, as it is, nothing is working. No error's showing in the console either, so, I can't seem to figure out where the problem is. I attached my app.js and UserAuthContext.js files below.
My SignUp code
import { useState } from "react";
import { useUserAuth } from "./context/UserAuthContext";
import { UserAuthContextProvider } from "./context/UserAuthContext";
function SignUp() {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [error, setError ] = useState("")
const {signUp} = useUserAuth ();
const handleSubmit = async (e) => {
e.preventDefault();
try{
await signUp(email, password);
} catch (err){
}
}
// React code omitted
}
and my UserAuthContext.js file:
import { createContext, useContext, useState, useEffect } from "react";
import {
createUserWithEmailAndPassword,
signInWithEmailAndPassword,
signOut,
onAuthStateChanged,
} from "firebase/auth";
import { auth } from "../firebase";
const userAuthContext = createContext();
export function UserAuthContextProvider({ children }) {
const [user, setUser] = useState("");
function signUp(email, password) {
return createUserWithEmailAndPassword(email, password);
}
function login(email, password) {
return signInWithEmailAndPassword(auth, email, password);
}
useEffect(() => {
const unsubscribe = onAuthStateChanged(auth, (currentUser) => {
setUser(currentUser);
});
return () => {
unsubscribe();
};
}, []);
return (
<userAuthContext.Provider value={{ user, signUp }}>
{children}
</userAuthContext.Provider>
);
}
export function useUserAuth() {
return useContext(userAuthContext);
}
I attached my firebase.js file, just in case
import { initializeApp } from "firebase/app";
import { getAuth } from "firebase/auth";
const firebaseConfig = {
// Settings for init app
};
// Initialize Firebase
const app = initializeApp(firebaseConfig);
export const auth = getAuth(app);
export default app;
After some questions, I think I understand your problem, when calling the createUserWithEmailAndPassword function, you are forgetting to pass the auth instance as the first parameter.
I suggest you make the following changes to the SignUp.
Note that I am using the function directly from firebase and not from the React Context.
import {
auth
} from "../firebase";
import {
createUserWithEmailAndPassword,
} from "firebase/auth";
const handleSubmit = async (e) => {
e.preventDefault();
if (email && password) {
createUserWithEmailAndPassword(auth, email, password)
.then(() => console.log("Created!"))
.catch((err) => console.log("Error!", err))
}
}
Related
I am trying to get my current user from getAuth() but it returns null.
import React, { useState, useEffect } from 'react'
import { getAuth } from 'firebase/auth'
const Profile = () => {
const [user, setUser] = useState(null)
const auth = getAuth()
useEffect(() => {
console.log(auth.currentUser)
}, [])
return <h1>Profile</h1>
}
export default Profile
I'm using firebase authentication and Oauth to sign in with GitHub. I need to write a custom hook for the refresh token.
Here is the code for my Firebase configuration.
import {initializeApp} from "firebase/app";
import {
getAuth,
signInWithPopup,
GithubAuthProvider,
signOut,
onAuthStateChanged,
NextOrObserver,
User,
} from 'firebase/auth';
const firebaseConfig = {
some codes
};
initializeApp(firebaseConfig);
const provider = new GithubAuthProvider();
export const auth = getAuth();
export const signInWithGitHubPopup = () => signInWithPopup(auth, provider);
export const userSignOut = async () => await signOut(auth);
export const onAuthStateChangedListener = (callback:NextOrObserver<User>) => onAuthStateChanged(auth, callback);
To track user authentication, I used React context and Firebase Observer pattern.
Here is the code for user.context.
import {createContext, useState, useEffect, ReactNode} from "react";
import {onAuthStateChangedListener} from "../utils/firebase/config";
import {User} from 'firebase/auth';
export const UserContext = createContext<User | null>(null);
export const UserProvider = ({children}: { children: ReactNode }) => {
const [currentUser, setCurrentUser] = useState<User | null>(null);
useEffect(() => {
const unSubscribe = onAuthStateChangedListener((user) => {
setCurrentUser(user);
});
return unSubscribe;
}, []);
return <UserContext.Provider value={currentUser}>{children}</UserContext.Provider>;
};
So wherever I need user auth Information like (access token and refresh token), I use the below code.
const currentUser = useContext(UserContext);
Now, I want to write an Axios interceptors and custom hook to refresh the token. The question is how can I do that?
I would appreciate that if you help me.
I'm building a react firebase app in which users can create their own profiles, sign in and add & delete meetups. Its currently doing something very weird in that, whenever I'm signed in as lets say user 1, I see said users email in the profile on the dashboard and their meetups. However, when I sign out user 1 and then sign in user 2, upon login I still see user 1's profile. Then I refresh the page, and waa-lah... now user 2's profile appears, but only after a page refresh and I am confused as to how to fix it as I have tried many different fixes. here is my firebase.js where I'm handling AuthContext and setting current user (this is the latest try, the commented out lines are what I had before now) using firebase v9.7.0
import { initializeApp } from "firebase/app";
import { getAuth, onAuthStateChanged } from "firebase/auth";
import {
getFirestore,
collection,
query,
where,
getDocs,
} from "firebase/firestore";
// import firebase from "firebase/compat/app";
// import "firebase/compat/firestore";
// import "firebase/compat/auth";
import { useState, useEffect, useContext, createContext } from "react";
const firebaseConfig = {
apiKey: "******************",
authDomain: "***************",
projectId: "***************",
storageBucket: "****************",
messagingSenderId: "************",
appId: "******************",
};
export const app = initializeApp(firebaseConfig);
export const firestore = getFirestore(app);
// export const app = firebase.initializeApp(firebaseConfig);
// export const firestore = firebase.firestore();
// const auth = app.auth();
const auth = getAuth();
export const AuthContext = createContext();
export const useAuthState = () => {
return useContext(AuthContext);
};
export const AuthContextProvider = ({ children }) => {
const [currentUser, setCurrentUser] = useState();
const [loading, setLoading] = useState(true);
useEffect(() => {
const unsubscribe = onAuthStateChanged(auth, (user) => {
if (user) {
// User is signed in, see docs for a list of available properties
// https://firebase.google.com/docs/reference/js/firebase.User
setCurrentUser(user);
setLoading(false);
// ...
} else {
console.log("user logged out");
setCurrentUser({});
// User is signed out
// ...
}
unsubscribe();
});
// const unsubscribe = auth.onAuthStateChanged((user) => {
// setCurrentUser(user);
// setLoading(false);
// });
// console.log(unsubscribe());
// return () => unsubscribe();
}, []);
const value = {
currentUser,
};
// useEffect(() => {
// setCurrentUser(auth.currentUser);
// setLoading(false);
// }, []);
return (
<AuthContext.Provider value={value}>
{!loading && children}
</AuthContext.Provider>
);
};
export default firestore;
And here is my Dashboard.js which handles the logout:
import React, { useState } from "react";
import { Card, Button, Alert } from "react-bootstrap";
import { getAuth, signOut } from "firebase/auth";
import { Link, useNavigate } from "react-router-dom";
import { useAuthState } from "../firebase";
export default function Dashboard() {
const [error, setError] = useState("");
const { currentUser } = useAuthState();
const navigate = useNavigate();
async function handleLogout() {
setError("");
try {
await signOut(getAuth());
sessionStorage.removeItem("token");
navigate("/login");
} catch {
setError("Failed to log out");
}
}
return (
<>
<Card>
<Card.Body>
<h2 className="text-center mb-4">Profile</h2>
{error && <Alert variant="danger">{error}</Alert>}
<strong>Email: </strong> {currentUser.email}
<Link to="/update-profile" className="btn btn-primary w-100 mt-3">
Update Profile
</Link>
</Card.Body>
</Card>
<div className="w-100 text-center mt-2">
<Button variant="link" onClick={handleLogout}>
Log Out
</Button>
</div>
</>
);
}
I'm thinking I need to clear the current user somewhere, but I'm not exactly sure if or where. Any guidance would be greatly appreciated, thank you!
In Firebase.js created the auth context and the logout function, and exports useAuth and the values as children.
// import signout function from firebase
import {
signOut,
} from 'firebase/auth'
// create auth context
const AuthContext = React.createContext()
export function useAuth() {
return useContext(AuthContext)
}
// singout function
async function logout() {
return signOut(auth)
}
// values for when we use "const { value } = useAuth();"
const value = {
currentUser,
login,
signup,
logout
}
In Dashboard.js we set the variable of logout after adding it to the exported values.
// import auth context
import { useAuth } from '../app/Firebase';
// set logout variable
const { logout } = useAuth();
// handle logout
async function handleLogout() {
setError("");
try {
await logout();
navigate("/login");
} catch {
setError("Failed to log out");
}
}
I have a problem with flickering my private routes while using my AuthContext. Below is the code for my Private Route:
import React from 'react';
import { Navigate } from 'react-router-dom';
import { UserAuth } from '../../Context/AuthContext';
const PrivateRoute = ({ children }) => {
const { user } = UserAuth();
if (!user) {
return <Navigate to='/login' />;
}
return children;
};
export default PrivateRoute;
No personal information shows, because the user is initialized to {} in Auth Context. but I can still see the page and navbar. Anyone have a solution?
Also, below is AuthContext.js:
import { createContext, useContext, useEffect, useState } from 'react';
import {
createUserWithEmailAndPassword,
signInWithEmailAndPassword,
signOut,
onAuthStateChanged,
} from 'firebase/auth';
import { auth } from '../../firebase';
const UserContext = createContext();
export const AuthContextProvider = ({ children }) => {
const [user, setUser] = useState({});
const createUser = (email, password) => {
return createUserWithEmailAndPassword(auth, email, password);
};
const signIn = (email, password) => {
return signInWithEmailAndPassword(auth, email, password)
}
const logout = () => {
return signOut(auth)
}
useEffect(() => {
const unsubscribe = onAuthStateChanged(auth, (currentUser) => {
//console.log(currentUser);
setUser(currentUser);
});
return () => {
unsubscribe();
};
}, []);
return (
<UserContext.Provider value={{ createUser, user, logout, signIn }}>
{children}
</UserContext.Provider>
);
};
export const UserAuth = () => {
return useContext(UserContext);
};
So I found a solution that's kind of cheeky. I'm not going to post my solution, but basically, I wrapped the return statement return children in the PrivateRoute function with an if statement for a specific item in the user object. This prevents any return and 'solves' the flicker.
Trying to implement protected routes but there is an error that occurs at the AuthUserProvider component. What I'm hoping to do is pass the firebase user information via the routes so I can send the user to the login page if they're not signed in. What is happening is that the user file is returning null even when a sign-in is performed.
Here is the error text:
Uncaught SyntaxError: Unexpected token o in JSON at position 1
at JSON.parse ()
at getUserFromCookie (userCookies.js:9)
at useUser.js:47
I've called the user data here and then sent it through to the children. Or I think I have. Here is the code in the AuthUserProvider file that i've attempted:
import { createContext, useContext, Context } from "react";
import useUser from "../firebase/useUser";
const authUserContext = createContext({
user: null,
});
export function AuthUserProvider({ children }) {
const auth = useUser();
return (
<authUserContext.Provider value={auth}>{children}</authUserContext.Provider>
);
}
// custom hook to use the authUserContext and access authUser and loading
export const useAuth = () => useContext(authUserContext);
Here is the useUser code where I have set up to call the user information via a token:
import { useEffect, useState } from "react";
import { useRouter } from "next/router";
import firebase from "firebase/app";
import "firebase/auth";
import initFirebase from "./initFirebase";
import {
removeUserCookie,
setUserCookie,
getUserFromCookie,
} from "./userCookies";
import { mapUserData } from "./mapUserData";
initFirebase();
export default function useUser() {
const [user, setUser] = useState();
const router = useRouter();
const logout = async () => {
return firebase
.auth()
.signOut()
.then(() => {
// Sign-out successful.
router.push("/auth");
})
.catch((e) => {
console.error(e);
});
};
useEffect(() => {
// Firebase updates the id token every hour, this
// makes sure the react state and the cookie are
// both kept up to date
const cancelAuthListener = firebase.auth().onIdTokenChanged((user) => {
if (user) {
const userData = mapUserData(user);
setUserCookie(userData);
setUser(userData);
} else {
removeUserCookie();
setUser();
}
});
const userFromCookie = getUserFromCookie();
if (!userFromCookie) {
router.push("/");
return;
}
setUser(userFromCookie);
return () => {
cancelAuthListener();
};
}, []);
return { user, logout };
}
Here is the getUseFromCookie & setUserCookie code:
import cookies from "js-cookie";
export const getUserFromCookie = () => {
const cookie = cookies.get("auth");
if (!cookie) {
return;
}
return JSON.parse(cookie);
};
export const setUserCookie = (user) => {
cookies.set("auth", user, {
expires: 1 / 24,
});
};
export const removeUserCookie = () => cookies.remove("auth");