User not staying signed in after successfully signing in? - reactjs

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.
// ...
}
});

Related

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
}, []);

Why won't my React Native Expo app prompt the user to give permission for notifications?

I am creating a React Native app for Android using Expo, EAS Build.
Following the docs here, I set up notifications for my app, including Firebase Cloud Messaging.
In my code, which is mostly taken from the docs, I check for notifications permission upon app launch using UseEffect, and if the app does not have permission, I request the permissions.
However, when I load the app on my development server, the alert pops up stating, "Failed to get push token for push notification!"
Just to make sure everything else is working, I went and enabled notification permission manually on my device via settings. Then, the app works great. The notifications I am scheduling work. There are no problems.
But obviously, I don't want the user to have to manually go to settings. I'd like the prompt to appear.
Could this be an issue with the development server which will no longer exist once deployed?
Any help appreciated. Thanks.
Here is what I believe to be the relevant code below from my App.js. I expected a prompt to appear for the user when they open the app the first time asking them to give notification permission.
import * as Notifications from "expo-notifications";
// other import statements
Notifications.setNotificationHandler({
handleNotification: async () => {
return {
shouldShowAlert: true,
shouldPlaySound: true,
shouldSetBadge: true,
};
},
});
// other code
export default function App() {
// other code
const notificationListener = useRef();
const responseListener = useRef();
useEffect(() => {
registerForPushNotificationsAsync().then(token => setExpoPushToken(token));
// This listener is fired whenever a notification is received while the app is foregrounded
notificationListener.current = Notifications.addNotificationReceivedListener(notification => {
setNotification(notification);
});
// This listener is fired whenever a user taps on or interacts with a notification (works when app is foregrounded, backgrounded, or killed)
responseListener.current = Notifications.addNotificationResponseReceivedListener(response => {
// console.log(response);
});
return () => {
Notifications.removeNotificationSubscription(notificationListener.current);
Notifications.removeNotificationSubscription(responseListener.current);
};
// other unrelated code
}, []);
// code related to the app itself
}
// below is the function i call above upon app launch in order to get notification
// but no prompt comes up for the user
async function registerForPushNotificationsAsync() {
let token;
if (Device.isDevice) {
console.log('about to getPermissionsAsync');
const { status: existingStatus } = await Notifications.getPermissionsAsync();
let finalStatus = existingStatus;
if (existingStatus !== 'granted') {
console.log('about to requestPermissionsAsync');
const { status } = await Notifications.requestPermissionsAsync();
console.log('the status gotten by requestPermissionsAsync() is the line below this: ');
console.log(status);
finalStatus = status;
}
if (finalStatus !== 'granted') {
alert('Failed to get push token for push notification!');
return;
}
console.log('about to get token');
token = (await Notifications.getExpoPushTokenAsync({
experienceId: '#johnquiet/buglecallexplore ',
})).data;
console.log('should see token below this line');
console.log(token);
} else {
alert('Must use physical device for Push Notifications');
}
if (Platform.OS === 'android') {
Notifications.setNotificationChannelAsync('alarms', {
name: 'Scheduled Notifications',
importance: Notifications.AndroidImportance.MAX,
vibrationPattern: [0, 250, 250, 250],
lightColor: '#a7e7fa',
});
}
return token;
}
// more unrelated code and styles
On Android 13 the prompt asking to grant the permission will not appear until at least one notification channel is created. Make sure to call Notifications.setNotificationChannelAsync before calling Notifications.getExpoPushTokenAsync.
https://docs.expo.dev/versions/latest/sdk/notifications/#permissions

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

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

How do I get the Firebase user object and use it in different React components?

I am using Firebase and trying to get the current signed in user. I check to see if a user is signed in using componentDidMount and if a user is signed in, I try and update my state.
Now, even though I am getting a Firebase user object in my console, the state is never updating. It also doesn't update when signing a user up for the first time using the toggleSignIn method.
All of this is being done in my App.js file. I'm wondering if there is a better way to go about trying to get the currently signed-in user and update the state accordingly so I can use the user object in other components and get things like the users photoURL, displayName, etc.
var App = React.createClass({
**getInitialState:** function () {
// Set initial state of user to null
return {
user : null
}
},
componentDidMount: function () {
// Checks to see if a user is signed in and sets state to the user object.
firebase.auth().onAuthStateChanged(function(user) {
if (user) {
**this.setState({
user: user
});**
} else {
// No user is signed in.
}
});
},
toggleSignIn: function () {
if (!firebase.auth().currentUser) {
// Create an instance of the Google provider object
var provider = new firebase.auth.GoogleAuthProvider();
// Sign in with popup.
firebase.auth().signInWithPopup(provider).then(function(result) {
var token = result.credential.accessToken;
// Get signed-in user info
**this.setState({
user: result.user
});**
// ...
}).catch(function(error) {
// Handle Errors here.
var errorCode = error.code;
var errorMessage = error.message;
// The email of the user's account used.
var email = error.email;
// The firebase.auth.AuthCredential type that was used.
var credential = error.credential;
// ...
});
} else {
// Sign user out
firebase.auth().signOut();
}
},
render: function () {
return (
<div className="app">
<main className="app-content">
{this.props.children}
</main>
</div>
);
}
});
module.exports = App;

Resources