Firebase auth not persisting on page refresh - reactjs

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");
}
});

Related

React - Session is lost when page is refreshed

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.

How to configure between pages role control automation in Next.js?

enter image description here
every time the route information changes, it should get the user's role information and then check it before going to the page he wants to go to.
If the permissions of the page and the user role match, it should load the page and allow access.
If there is no access permission, it should redirect the user to the error page without loading the page.
I need an algorithm that will capture all pages in "user_app" in "Next.js" and manage access by providing control.
1- When the user wants to enter a page, I got the path information.
2- I got the data where the page permissions are defined in the process.env with the path information.
3- I got the user role information from the cookie/token.
4- I compared the role information with the page permissions. If the user has permission, they can enter the page, if not, they will be redirected to the 404 page.
Problem: This control structure works when the user enters the page he wants to go to. and it makes that page accessible for a very short time. (while the control function is running)... as I added in the picture, the new page should be checked before loading and after the permission is granted, the page data should be loaded and run.
note: this function is triggered in app_js every time the page changes.
import React from 'react';
import axios from 'axios';
import UserLogout from '../UserLogout';
import decrypted from '../crypto/decrypted';
import { useRouter } from 'next/router';
export default async function RolePageCheck() {
try {
const router = useRouter();
//kullanıcı hangi sayfada onu alıyoruz.
const path = router.pathname;
//public erişebilir sayfaları aldık
//sadece izinle girilebilen sayfaları aldık
const pageRoles = process.env.pageLinks[path];
const res = await axios.get(`${process.env.NEXT_PUBLIC_API_URL}/token/${"GET"}`).then((value) => {
//cookie var ise kullanıcı bilgilerini alıyoruz.
if(value.data.Acoount !== null && value.data.Acoount !== undefined && value.data.success === true){
const decryptedValue = decrypted(value.data.Acoount);
const userRole = decryptedValue.role;
if(pageRoles.includes(userRole) && ( !pageRoles.includes(0) || path==="/")){
return true;
}
else{
return router.replace("/404");
}
}
else if(pageRoles.includes(0)){
return true;
}
else{
return router.replace("/404");
}
});
} catch (error) {
return { success: false, message: error.message };
}
}
you can achieve this with router.events
useEffect(() => {
// on initial load - run auth check
authCheck(router.asPath);
// on route change start - hide page content by setting authorized to false
const hideContent = () => setAuthorized(false);
const logAction = () => {
console.log('routeChangeStart');
};
router.events.on('routeChangeStart', logAction);
router.events.on('routeChangeStart', hideContent);
// on route change complete - run auth check
router.events.on('routeChangeComplete', authCheck);
// unsubscribe from events in useEffect return function
return () => {
router.events.off('routeChangeStart', logAction);
router.events.off('routeChangeStart', hideContent);
router.events.off('routeChangeComplete', authCheck);
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

React Next js app redirect to login is premature

After a lot of searching for several hours, I have the following code to redirect from a user profile page if not logged in.
NOTE: Simply showing a not authorized page is easy but its the redirect thats messing things up.
The code does the job of redirecting when user is not logged in.
const Dashboard = () => {
const [user, { mutate }] = useCurrentUser();
const router = useRouter();
useEffect(() => {
// redirect to login if user is not authenticated
if (!user) router.push('/login');
}, [user]);
...
The problem is when a user is logged in and directly goes to /user/dashboard route, for a split second, user is undefined may be so it redirects to login. When it gets to login, it finds that user is authenticated so redirects to home page because I am redirecting a logged in user to home page.
How to prevent that split second of "not a user" status when page is first loading?
I tried -
getInitialProps
getServerSideProps - Cant use router because next router can only be used on client side
componentDidMount - UseEffectI tried above is the equivalent correct?
Edit: Based on answer below, I tried this but still directly takes user to login first. I am using react cookies and I do see loggedIn cookie as true when user is logged in and its not set when user is not logged in.
Dashboard.getInitialProps = ({ req, res }) => {
console.log(req.headers.cookie)
var get_cookies = function(request) {
var cookies = {};
request.headers && request.headers.cookie.split(';').forEach(function(cookie) {
var parts = cookie.match(/(.*?)=(.*)$/)
cookies[ parts[1].trim() ] = (parts[2] || '').trim();
});
return cookies;
};
//console.log(get_cookies(req)['loggedIn']);
if (get_cookies(req)['loggedIn'] == true) {
console.log("entered logged in")
return {loggedIn: true};
}
else {
console.log("entered not logged in")// can see this on server console log
// User is not logged in, redirect.
if (res) {
// We're on the server.
res.writeHead(301, { Location: '/login' });
res.end();
} else {
// We're on the client.
Router.push('/login');
}
}
}
You can implement redirect when not authenticated in getServerSideProps
Below example is based on JWT Authentication with cookies.
export const getServerSideProps = async (ctx) => {
const cookie = ctx.req.headers.cookie;
const config = {
headers: {
cookie: cookie ?? null
}
}
let res;
try {
// your isAuthenticated check
const res = await axios('url', config);
return { props: { user: res.data } };
} catch (err) {
console.error(err);
ctx.res.writeHead(302, {
Location: 'redirectUrl'
})
ctx.res.end();
return;
return { props: { user: null } };
}
}
You should be able to use getInitialProps to redirect. You just need to check whether you're on the server or the client and use the proper redirect method. You can't use hooks in getInitialProps so your useCurrentUser approach won't work and you'll need some other way to check whether the user is authed. I don't know anything about the structure of your application, but it's probably just some kind of request to wherever you're storing the session.
import Router from 'next/router';
const Dashboard = (props) => {
// props.user is guaranteed to be available here...
};
Dashboard.getInitialProps = async ({ res }) => {
// Check authentication.
// Await the response so that the redirect doesn't happen prematurely.
const user = await ...
// User is logged in, return the data you need for the page.
if (user) {
return { user };
}
// User is not logged in, redirect.
if (res) {
// We're on the server.
// Make the redirect temporary so it doesn't get cached.
res.writeHead(307, { Location: '/login' });
res.end();
} else {
// We're on the client.
Router.push('/login');
}
};
After many hours of struggle, there was one number that was breaking this.
Instead of
res.writeHead(301, { Location: '/login' });
I used
res.writeHead(307, { Location: '/login' });
and it worked.
301 is a permanent redirect so if we use that, when the user logs in, the browser still holds the redirect cache.
From next js docs
Next.js allows you to specify whether the redirect is permanent or not with the permanent field. This is required unless you need to specify the statusCode manually
When permanent is set to true we use a status code of 308 and also set a Refresh header for backwards compatibility with IE11.
When permanent is set to false we use a status code of 307 which is not cached by browsers and signifies the redirect is temporary.
Next.js permits the following status codes:
-301 Moved `Permanently`
-302 Found
-303 See Other
-307 `Temporary` Redirect
-308 Permanent Redirect

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!

User not staying signed in after successfully signing in?

I'm integrating Firebase's google authentication into my React application and am running into an issue where the user is successfully signing in but when the page is refreshed he is no longer logged in.
I have a file Firebase.js where all my firebase functionality is located:
if (!firebase.apps.length) {
fb = firebase.initializeApp(config);
} else {
fb = firebase.apps[0];
}
module.exports = {
app: fb,
db: fb.database(),
auth: fb.auth,
isUserSignedIn: () => {
var user = fb.auth.currentUser;
if (user) {
return true;
} else {
return false;
}
},
signIn: () => {
var provider = new firebase.auth.GoogleAuthProvider();
fb.auth().signInWithPopup(provider).then(function(result) {
console.log("Signed in using Google.");
}).catch(function(error) {
console.log("Error occured with sign in.");
location.reload();
});
},
}
My app successfully loads the google sign in pop up and I get the message in the console "Signed in using Google."
However, in the React component where I'm checking whether the user is signed in, when the page refreshes it says the user isn't signed in:
import fb from '../../../Firebase';
if (typeof window !== 'undefined') {
const container = document.getElementById('navBar');
let isSignedIn = fb.isUserSignedIn();
console.log("Signed in: " + isSignedIn);
ReactDOM.render(<UserLink props={{signedIn: isSignedIn }} />, container);
}
I checked in the firebase auth console and know for a fact that the user got authenticated, but for some reason my app is not picking up on that.
Can anybody please help me see what I'm doing anything wrong?
Use an auth state change callback to get the login state rather than depending on it being immediately available when your code starts running at page load. An example is in the documentation.
firebase.auth().onAuthStateChanged(function(user) {
if (user) {
// User is signed in.
} else {
// User is signed out.
// ...
}
});

Resources