React - Session is lost when page is refreshed - reactjs

I am using Firebase, and after login to watch if user is logged in using the following block;
useEffect(() => {
const checkAuthToken = () => {
const token = sessionStorage.getItem('auth-token');
if (token) {
setIsAuthenticated(true);
} else {
setIsAuthenticated(false);
}
}
window.addEventListener('storage', checkAuthToken);
return () => {
window.removeEventListener('storage', checkAuthToken);
}
}, [])
It works without issue until I refresh the page even sessionStorage data is still on the browser memory.

The idiomatic way to respond to sign in with Firebase is to listen for auth state changes, rather than reading something from local storage yourself.
I recommend sticking to that approach, which is shown in the first snippet in the documentation on getting the current user.

Related

Supabase onAuthStateChange() triggers when switching tabs in React

I have the following code in my React project using Supabase:
// supabaseClient.ts
export const onAuthStateChangedListener = (callback) => {
supabase.auth.onAuthStateChange(callback);
};
// inside user context
useEffect(() => {
const unsubscribe = onAuthStateChangedListener((event, session) => {
console.log(event);
});
return unsubscribe;
}, []);
However, every time I switch tabs away from the tab rendering the website to something else, and back, I see a new log from this listener, even if literally no change happened on the website.
Does anyone know the reason for this? The useEffect inside my user context component is the only place in my app where the listener is being called. To test, I wrote this dummy function inside my supabaseClient.ts file:
const testFunction = async () => {
supabase.auth.onAuthStateChange(() => {
console.log("auth state has changed");
});
};
testFunction()
This function also renders every time I switch tabs. This makes it a little annoying because my components that are related to userContext re render every time a tab is switched, so if a user is trying to update their profile data or something, they cannot switch tabs away in the middle of editing their data.
Supabase onAuthStateChange by default triggers every time a tab is switched. To prevent this, when initializing the client, add {multiTab: false} as a parameter.
Example:
const supabase = createClient(supabaseUrl, supabaseAnonKey, {multiTab: false,});
Here is my solution to the same problem. The way I've found is saving the access token value in a cookie every time the session changes, and retrieve it when onAuthStateChange get triggered, so I can decide to not update anything if the session access token is the same.
// >> Subscribe to auth state changes
useEffect(() => {
let subscription: Subscription
async function run() {
subscription = Supabase.auth.onAuthStateChange(async (event, newSession) => {
// get current token from manually saved cookie every time session changes
const currentAccessToken = await getCurrentAccessToken()
if (currentAccessToken != newSession?.access_token) {
console.log('<<< SUPABASE SESSION CHANGED >>>')
authStateChanged(event, newSession)
} else {
console.log('<<< SUPABASE SESSION NOT CHANGED >>>')
}
}).data.subscription
// ** Get the user's session on load
await me()
}
run()
return function cleanup() {
// will be called when the component unmounts
if (subscription) subscription.unsubscribe()
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])

Firebase auth not persisting on page refresh

For some reason when I refresh the page the authentication doesn't persist and itll redirect the user back to sign in, I've seen a couple other postings about this on here but still doesn't work in my case. I'm using nextjs and I have localhost added to my signin method on firebase, it won't let me add localhost:3000. I don't want the user to be redirected to the sign in I want it to be persisted, so however many times the user refreshes the page it stays the same. I'm using nextjs, Here is my code:
useEffect(() => {
firebase.auth().onAuthStateChanged((user) => {
if (user) {
const userDetails = fetchDetails(user.uid);
userDetails.then((use) => setUser(use.data()));
} else {
router.push("/signin");
}
});
}, []);
I've also tried it just like this too:
firebase.auth().onAuthStateChanged((user) => {
if (user) {
const userDetails = fetchDetails(user.uid);
userDetails.then((use) => setUser(use.data()));
} else {
router.push("/signin");
}
});

How can I stay the user in the same page?

Every time I reload the my account page, it will go to the log in page for a while and will directed to the Logged in Homepage. How can I stay on the same even after refreshing the page?
I'm just practicing reactjs and I think this is the code that's causing this redirecting to log-in then to home
//if the currentUser is signed in in the application
export const getCurrentUser = () => {
return new Promise((resolve, reject) => {
const unsubscribe = auth.onAuthStateChanged(userAuth => {
unsubscribe();
resolve(userAuth); //this tell us if the user is signed in with the application or not
}, reject);
})
};
.....
import {useEffect} from 'react';
import { useSelector } from 'react-redux';
const mapState = ({ user }) => ({
currentUser: user.currentUser
});
//custom hook
const useAuth = props => {
//get that value, if the current user is null, meaning the user is not logged in
// if they want to access the page, they need to be redirected in a way to log in
const { currentUser } = useSelector(mapState);
useEffect(() => {
//checks if the current user is null
if(!currentUser){
//redirect the user to the log in page
//we have access to history because of withRoute in withAuth.js
props.history.push('/login');
}
// eslint-disable-next-line react-hooks/exhaustive-deps
},[currentUser]); //whenever currentUser changes, it will run this code
return currentUser;
};
export default useAuth;
You can make use of local storage as previously mentioned in the comments:
When user logs in
localStorage.setItem('currentUserLogged', true);
And before if(!currentUser)
var currentUser = localStorage.getItem('currentUserLogged');
Please have a look into the following example
Otherwise I recommend you to take a look into Redux Subscribers where you can persist states like so:
store.subscribe(() => {
// store state
})
There are two ways through which you can authenticate your application by using local storage.
The first one is :
set a token value in local storage at the time of logging into your application
localStorage.setItem("auth_token", "any random generated token or any value");
you can use the componentDidMount() method. This method runs on the mounting of any component. you can check here if the value stored in local storage is present or not if it is present it means the user is logged in and can access your page and if not you can redirect the user to the login page.
componentDidMount = () => { if(!localStorage.getItem("auth_token")){ // redirect to login page } }
The second option to authenticate your application by making guards. You can create auth-guards and integrate those guards on your routes. Those guards will check the requirement before rendering each route. It will make your code clean and you do not need to put auth check on every component like the first option.
There are many other ways too for eg. if you are using redux you can use Persist storage or redux store for storing the value but more secure and easy to use.

How to wait for auth state to load on the client before fetching data

Recently I stumbled across the useAuth and useRequireAuth hooks: https://usehooks.com/useRequireAuth/. They are incredibly useful when it comes to client-side authentication. However, what's the correct way for waiting until auth data is available to fetch some other data? I've come up with the following code:
const Page = () => {
// makes sure user is authenticated but asynchronously, redirects if not authenticated, short screen-flash
useRequireAuth()
// actual user object in state, will be updated when firebase auth state changes
const user = useStoreState((state) => state.user.user);
if (!user) {
return <div>Loading</div>
}
useEffect(() => {
if (user) {
fetchSomeDataThatNeedsAuth();
}
}, [user]);
return (
<h1>Username is: {user.name}</h1>
)
}
Is this a "good" way to do it or can this be improved somehow? It feels very verbose and needs to be repeated for every component that needs auth.
This looks fine to me. The thing you could improve is that your useRequireAuth() could return the user, but that's up to you.
Additionally, you probably should check if user is defined before rendering user.name.

Remember me with redux storage

I have userDetails in redux store and I used redux persist with redux storage to save the details after loads page or after closing my app.
The problem is, when I logged in and I don't checked the rememberMe in index.js is checked if !rememberMe and do logOut() right after login.
So I need to check just at the entrance into the app for the first time and if !rememberMe I will call logOut() but if userDetails !== 'emepty' && rememberMe I will do login() for get valid token.
Why I need to check this it in the first time? because I need to reset the uesrDetails to guest details if !rememberMe.
This is my index.js:
const setBeforeLists = useCallback(
(lists) => {
beforeListsChanged(lists);
},
[beforeListsChanged]
);
const setUserDetails = useCallback(
(userDetails) => {
userDetailsChanged(userDetails);
},
[userDetailsChanged]
);
useEffect(() => {
//userDetails.id === 'empty' is guest.
if (
userDetails.id !== 'empty' &&
!userDetails.rememberMe
) {
logOut(setUserDetails, setBeforeLists);
}
}, [
setBeforeLists,
setUserDetails,
userDetails
]);
When you refresh the app, the store will be reset. The token is gone. As we expected.
So, we can’t use the state to store the authentication token. This is where AsyncStorage comes onto the stage.
I'm replying to a react-native application.
If it's a web application use session storage.
Let’s import AsyncStorage in the React Native project:
import { AsyncStorage } from "react-native";
Then, let’s create two functions,setToken and getToken, for storing and retrieving the persistent data. These are the main methods of this tutorial. Here, we are storing and retrieving the user data from/to AsyncStorage.
async storeToken(user) {
try {
await AsyncStorage.setItem("userDetails", JSON.stringify(user));
} catch (error) {
console.log("Something went wrong", error);
}
}
async getToken(user) {
try {
let userDetails = await AsyncStorage.getItem("userDetails");
let data = JSON.parse(userDetails);
console.log(data);
} catch (error) {
console.log("Something went wrong", error);
}
}
componentDidMount() {
this.getToken();
}
Let’s prove it. The token is thereafter you quit & reopen the app. Try it out yourself. Now, our app automatically logs you in after each session.
Hope this helps!

Resources