Keep logged in between reloads - Firebase (Sign in with Google) - javascript

How do you keep a user logged in with Sign in with Google between reloads?
Here is my login function (Firebase):
const loginWithGoogle = async () => {
try {
const provider = new firebase.auth.GoogleAuthProvider();
const res = await firebase.auth().signInWithPopup(provider);
$user = res.user;
$isLoggedIn = true;
}
catch (error) {
console.log(error);
}
}
Although $isLoggedIn and the $user object save to LocalStorage (I'm using SvelteJS), the user does not actually stay logged in after reloading.
After reloading, admin users are no longer able to write to the database and I get the error firestore: PERMISSION_DENIED: Missing or insufficient permissions.
(Here are my firestore rules)
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read;
allow write: if (request.auth != null && (request.auth.uid == "someAdminID" || request.auth.uid == "otherAdminID"));
}
}
How would I stay logged in after reloading? Or is there some way to automatically log in again if the user had not previously logged out?

On most browser environments Firebase automatically persists the user credentials in local storage, and restores them when the app/page reloads. This requires it to make a call to the server however, a.o. to check if the account was disabled, and thus it isn't completed right away when the page loads.
To react to when the authentication state is restored (or otherwise changes), you'll want to use an auth state listener as shown in the first code snippet in the documentation on getting the currently signed in user:
firebase.auth().onAuthStateChanged((user) => {
if (user) {
// User is signed in, see docs for a list of available properties
// https://firebase.google.com/docs/reference/js/firebase.User
var uid = user.uid;
// ...
// 👈 This is where you can also query the database as the user for the first time
} else {
// User is signed out
// ...
}
});

Related

How to store firebase authentication session

I have a web application made in vue with firebase authentication. The problem here is that I need to login every time I reload the page.
Firebase Authentication by default stores the user's credentials in local storage of the browser, and restores it from there when the page reloads. This requires a call to the server though, which means that if you access Firebase.auth().currentUser immediately as the page loads, it might not be set yet.
To prevent having this problem, use an auth state listener as shown in the first snippet of the documentation on getting the current user.
For v8 and earlier of the SDK that'd be:
firebase.auth().onAuthStateChanged((user) => {
if (user) {
// User is signed in, see docs for a list of available properties
// https://firebase.google.com/docs/reference/js/firebase.User
var uid = user.uid;
// ...
} else {
// User is signed out
// ...
}
});
For v9:
import { getAuth, onAuthStateChanged } from "firebase/auth";
const auth = getAuth();
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
const uid = user.uid;
// ...
} else {
// User is signed out
// ...
}
});

Firebase Web SDK: refreshing auth token so that email_verified is updated in firestore rules

Using Firebase Web SDK, I'm requiring users to verify their email before accessing Firestore documents. I have a Firestore rule that gates the document like this:
allow read: if request.auth != null && request.auth.token.email_verified;
I'd like the email verification to be reflected as soon as the user verifies his/her email without requiring the user to sign out and sign back in. Unfortunately onAuthStateChanged() doesn't fire when emailVerified changes, so I'm refreshing the client token by polling for changes to emailVerified. Something like this:
Note: My examples use the new beta Firebase Web SDK V9 (Modular Web SDK) in case the syntax is unfamiliar.
window.setInterval(() => {
reload(auth.currentUser).then(() => {
if (!auth.currentUser?.emailVerified)
return;
// unsubscribe the previous onAuthStateChanged() listener
unsubscribe();
// resubscribe to auth changes
unsubscribe = auth.onAuthStateChanged(user => {
// Yay! user.emailVerified is now true
console.log(user.emailVerified);
});
});
}, 2000);
With the code above, I can get emailVerified to be reflected property inside my web app, but the problem arises when I try to make a request to Firestore:
const unsubscribe = onSnapshot(
doc(db, 'widgets', 'widget1'),
snap => {
console.log(snap);
},
);
That request results in a Firestore permission error. Once the user signs out and signs back in, the Firestore request is accepted.
How can I get the auth token that gets sent to Firestore to be updated with the latest email_verified without the user to sign out and and sign back in?
It turns out that a series of steps need to happen to refresh the token. After email verification, you need to reload the user AND explicitly get a new id token with getIdToken(user, true) after you reload the user. Only after those 2 steps will an updated token be sent to Firestore for queries. You also need to unsubscribe and re-subscribe to onAuthStateChanged manually, as that doesn't get triggered on token change. The modified version of my example is:
window.setInterval(() => {
reload(auth.currentUser).then(() => {
if (!auth.currentUser?.emailVerified)
return;
getIdToken(auth.currentUser, true).then(() => {
// now the new token will be sent to Firestore, yay!
// unsubscribe the previous onAuthStateChanged() listener
unsubscribe();
// resubscribe to auth changes
unsubscribe = auth.onAuthStateChanged(user => {
// Yay! user.emailVerified is now true
console.log(user.emailVerified);
});
})
});
}, 2000);
Please post your answer if there's an easier way. I especially don't like the polling part.

Firebase email/password authentication in electron

I did firebase authentication with email/password in my electron app, and it works, but only on first page. When I go to second page, I'm no longer signed in. Because I'm new to elecetron.js and firebase as well I used this tutorial:https://www.youtube.com/watch?v=bWS0ocfszmE.
login.js
loginBtn.addEventListener('click', function() {
var emailField = document.getElementById('email').value;
var passwordField = document.getElementById('password').value;
firebase.auth().signInWithEmailAndPassword(emailField, passwordField).then(function() {
document.location.href = 'mainPage.html';
console.log(firebase.auth().currentUser);
}).catch(function(error) {
if (error != null) {
console.log(error.message);
alertify.error(error.message);
return;
}
});
secondpage.js
var firebase = require("firebase/app");
require("firebase/auth");
console.log(firebase.auth().currentUser);
I expected the output in console with user that I signed in, but get null.
The problem is that on each new page Firebase Authentication will check whether the user's sign-in token is still valid. Since this may take some time (and require calling a server), this happens asynchronously. And by the time your console.log(firebase.auth().currentUser) runs, the process hasn't completed yet.
That's why you'll want to use an onAuthStateChanged listener to detect the authentication state of the user:
firebase.auth().onAuthStateChanged(function(user) {
if (user) {
// User is signed in.
} else {
// No user is signed in.
}
});

Validate displayName firebase authentication

I'm currently working with Firebase Authentication and absolutely loving it. The only problem right now is that I'm trying to validate the user's display name using my own rules. What I have tried is to use Cloud Firestore to store the display name and other related info such as address, etc. On registration, the user has to declare his/her display name as well as email and password. Somewhere in my code I'm doing this:
try {
await firebase.auth().createUserWithEmailAndPassword(email.value, password.value);
await firebase
.firestore()
.collection('/users')
.doc(firebase.auth().currentUser.uid)
.set({
displayName: displayName.value,
});
} catch (err) {
setIsLoading(false);
setErrorText(err.message);
}
The problem is that the app also redirects the user when he/she finishes his/her registration to the private area like so:
useEffect(() => {
if (authUser) history.push('/dashboard');
}, [authUser, history]);
(authUser is stored in Redux and is updated inside onAuthStateChanged()) The display name won't be validated because as soon as firebase.auth().createUserWithEmailAndPassword() resolves, authUser has a value and immediately redirects the user. It's kind of annoying and would love to know whether there's a better way of organising this bit of code.
Any help is greatly appreciated!
Edit: Firestore rules:
service cloud.firestore {
match /databases/{database}/documents {
match /users/{documentId} {
allow read: if request.auth != null
allow write: if request.auth != null &&
request.resource.data.displayName is string &&
request.resource.data.displayName.size() >= 6 &&
request.resource.data.displayName.size() <= 20
}
}
}

Firebase cloud firestore + auth: write only for signed in users

I am building a vuejs application and I am trying to add firebase auth to the app. I used cloud firestore for a news system.
Now on the "add news" page I call
firebase.auth().signInWithEmailAndPassword("john#doe.com", "mypassword").catch(function (error) {
console.log(error.code);
console.log(error.message);
if (errorCode === 'auth/wrong-password') {
alert('Wrong password.');
} else {
alert(errorMessage);
}
});
to log the user in, or give him feedback if something went wrong.
Later I am writing data to cloud firestore like this
irestore.collection("news").doc().set({
date: today,
title: "Hello",
text: "A think I am a news!"
});
In the firestore rules I set
allow read, write: if request.auth != null;
to the news collection - so it should only grant write access to logged in users, right?
Now the thing:
If i log in with a wrong password, firebase gives back, that the password was incorrect (so we are not logged in are we?) but the data is written to firestore anyways. What did I do wrong?
Your rule request.auth != null will check if the user is signed in via any method. You can check on the client side for sign in two ways:
Sync:
// Synchronously check for current user
var user = firebase.auth().currentUser;
if (user) {
// User is signed in.
} else {
// No user is signed in.
}
Async:
// Listen for current user status
firebase.auth().onAuthStateChanged(function(user) {
if (user) {
// User is signed in.
} else {
// No user is signed in.
}
});
If you get a user then you can expect request.auth to not be null.

Categories

Resources