I'm using Firebase Auth in a Vue app. I want signed-in users to be persisted through reloading and closing the browser, so I'm using the 'local' persistence setting, which saves logged-in user tokens into the browser's localStorage.
The docs state that Firebase will automatically detect this localStorage entry, and publish a change in auth state, which fires onAuthStateChange handlers.
However, in my app the user credential is not being retrieved after a refresh.
The token is stored properly and can be viewed in the dev tools, so the browserLocalPersistence is working.
When I reload the page, however, the onAuthStateChange hook fires only one time, printing that the current user is null.
Code
Firebase init
const app = initializeApp(firebaseConfig);
const auth = initializeAuth(app);
setPersistence(auth, browserLocalPersistence)
Pinia store w/ action to update state:
export const useAuthStore = defineStore({
id: "auth",
state: (): UserState => ({
user: null,
}),
actions: {
init() {
return new Promise<User | null>((resolve, reject) => {
onAuthStateChanged(
getAuth(firebaseApp),
async (user) => {
console.log(user);
this.user = user;
resolve(user);
},
reject
);
});
},
},
});
Initializing onAuthStateChanged hook within Pinia store from setup function of top-level Vue component:
const authStore = useAuthStore();
authStore.init().then(console.log);
With this code, I'm getting null printed to the console. That's understandable upon immediate page load, before Firebase has verified the persisted authentication, but I would expect the callback to fire a second time with the correct credentials loaded.
Everything works properly after an initial sign in -- it's only after a refresh that the credentials are not loaded from local storage.
Any help is appreciated!
Related
Currently I'm building an app that on user signup navigates the user to an email verification page. This page then watches the firebase user object inside of a Pinia store waiting for the emailVerified user property to update before directing them to a new page.
When I update the user object manually using vue devtools I can observe my console.log. When I receive the email verification email and use the link provided by firebase my watcher does not react to the user update. I can refresh the pinia store using my vue devtools and I see emailVerified inside my firebase user object has been updated to true but my watcher was never hit.
Any ideas on why I am losing reactivity when going through the email flow?
testStore.js
export const useTestStore = defineStore('test', () => {
const auth = getAuth()
const {user} = useAuth(auth)
return {
user: user,
}
})
emailVerification.js
<script setup>
const { user } = storeToRefs(testStore)
watch(user, () => {
console.log('Direct user to new page')
}, { deep:true })
</script>
For some reason when replacing my watcher with setInterval it seems to works... although this is not the ideal solution
setInterval(function () {
if(user){
if(user.value?.emailVerified) {
console.log('Direct user to new page');
}
}
}, 5000);
Hi every one I want to set Auth state persistence on firebase to local i'm working on flutter and I don't know how to do that
I found this on firebase web site
import { getAuth, setPersistence, signInWithEmailAndPassword, browserSessionPersistence } from "firebase/auth";
const auth = getAuth();
setPersistence(auth, browserSessionPersistence)
.then(() => {
// Existing and future Auth states are now persisted in the current
// session only. Closing the window would clear any existing state even
// if a user forgets to sign out.
// ...
// New sign-in will be persisted with session persistence.
return signInWithEmailAndPassword(auth, email, password);
})
.catch((error) => {
// Handle Errors here.
const errorCode = error.code;
const errorMessage = error.message;
});
but not sure how I do that on flutter if any one can help with that
As per the FlutterFire documentation
On native platforms such as Android & iOS, this behavior is not configurable and the user's authentication state will be persisted on-device between app restarts. The user can clear the apps cached data via the device settings which will wipe any existing state being stored.
If you're using Flutter for a web app, by default the auth state is stored in local storage. If you want to change this to session based or no persistence you would set that like this:
// Disable persistence on web platforms
await FirebaseAuth.instance.setPersistence(Persistence.NONE);
I have an app with a LOGIN page and a REGISTER page. Both pages have a "Sign in with Google" button, as well as a regular login and password input form for those that don't want to sign in with Google. I am also using FireStore to create user profile documents for registered users. When the user also logs in, the app will query the user's profile for use throughout the app. This all works fine.
I noticed that a google user does not need to "register"...he can still click the login button and it will "sign him up" automatically because that's how Google Auth Provider works. However, since he did not "register", he does not yet have a profile. In this case, I had to write some logic so a profile would be created for a Google user. Although this logic works, I just wonder if this is the best way to do this. Are there best practices for handling Google/Social logins for people skipping the traditional "registering" pages? I know most people would probably head to the register page and register, but there will undoubtedly be some people that will skip that and go start to the LOGIN page and sign in via Google that way.
Here's how I'm handling the login page with Google login button:
login.vue
async logInWithGoogle() {
try {
const provider = new this.$fireAuthObj.GoogleAuthProvider()
const userCredentials = await this.$fireAuth.signInWithRedirect(
provider
) ....
Then in my Store (in my case, Vuex state management pattern), I have the following actions:
store.js
First, this onAuthStateChanged observer will notice the new user state and do the following code:
async onAuthStateChangedAction({ commit, dispatch }, { authUser }) {
if (authUser) {
console.log('user committing from onAuthStateChangedAction...')
commit('SET_CURRENT_USER', authUser)
console.log(
'fetchUserProfile action dispatching from onAuthStateChangedAction...'
)
await dispatch('fetchUserProfile', authUser)
} else {
dispatch('logOutUser')
}
}
That onAuthStateChanged observer will fetch the user's profile (and this is the logic I am concerned with...not sure if this is an ideal way to handle user's logging in via Google for first time and bypassing registration:
async fetchUserProfile({ commit }, user) {
try {
const docRef = this.$fireStore.collection('users').doc(user.uid)
const profile = await docRef.get()
if (profile.exists) {
commit('SET_USER_PROFILE', await profile.data())
console.log(
'user profile EXISTS and set from fetchUserProfile action'
)
} else {
console.log('profile does not exist! Creating...')
await docRef.set({
displayName: user.displayName,
email: user.email,
uid: user.uid,
photoUrl: user.photoURL,
providerId: user.providerData[0].providerId,
createdAt: this.$fireStoreObj.FieldValue.serverTimestamp()
})
const p = await docRef.get()
commit('SET_USER_PROFILE', await p.data())
console.log('user profile set')
}
} catch (error) {
console.log('can not fetch profile', error)
}
},
Thanks for any tips or assurances that I am on the right (or wrong) path on handling this. Thank you!
Why not create an empty document with the user's uid and prompt them to "complete their profile"? Until they do so, force redirect them back to the profile page indefinitely.
I've a project in which I used to authenticate the users with firebase-auth.In my project users can not create their accounts on their own.Only admin have the privilege to add the user accounts.
In order to use onAuthStateChanged() function I must use firebase-auth in my page.But the issue is because of using firebase-auth on client side one can esaily create accounts by running createUserWithEmailAndPassword() function on the console without having the admin privilege.
Now how can I restrict the people from using createUserWithEmailAndPassword() function on client side?
The only way you can stop clients apps from creating accounts is to disable all authentication providers for your project in the Firebase console. You could write an auth onCreate Cloud Function that attempts to figure out if a new account was created by client or admin code if you want to try to delete it immediately.
I think you can add a claim once the user is added, via a cloud function, which requires authorization, so that if the user doesn't have that claim he can't use the app or can't login.
In 2022 with Firebase Auth with Identity Platform and blocking functions, we can accomplish that the following way:
Create an HTTP function that receives email, password and displayName, and creates user using firebase-admin:
import { https } from 'firebase-functions';
import { getAuth } from 'firebase-admin/auth';
import cors from 'cors';
const auth = getAuth();
// Register an HTTP function with the Functions Framework
export const signupUser = https.onRequest((req, res) => {
const options = {
origin: 'http://localhost:3000'
};
cors(options)(req, res, () => {
console.log('all good');
auth
.createUser({
email: 'example#email.com',
emailVerified: false,
password: 'secretPassword',
displayName: 'John Doe',
disabled: false,
})
.then((userRecord) => {
// See the UserRecord reference doc for the contents of userRecord.
console.log('Successfully created new user:', userRecord.uid);
})
.catch((error) => {
console.log('Error creating new user:', error);
});
// Send an HTTP response
res.send('OK');
});
});
Modify response and origin in CORS as you need.
Now create a blocking beforeCreate function and check for user's display name, if there is no display name, throw an error:
import { auth } from "firebase-functions";
import { initializeApp, applicationDefault } from 'firebase-admin/app';
import { getAuth } from 'firebase-admin/auth';
import postmark from 'postmark';
const app = initializeApp({
credential: applicationDefault(),
projectId: 'your_project_id',
});
const tnc = getAuth(app);
export const signUp = auth
.user().beforeCreate((user, context) => {
if (!user.displayName) {
throw new auth.HttpsError('permission-denied');
}
});
This will work because there is no way to include "display name" when signing up via client side
So you, in short, point is to create a Cloud Function that will register users and make sure to add the check to beforeCreate for something that you know is only possible to do on server-side via firebase-admin sdk.
EDIT: CORRECTION
Just found out you can now disable client side signup from Firebase Console if you have Auth + Identity Platform
Background
I am using GoogleAuthProvider, with the default LOCAL persistence.
When I navigate to the page, I do:
firebase.initializeApp(firebaseConfig)
firebase.auth().currentUser // this is always null
firebase.auth().onAuthStateChanged(user => {
console.log("authStateChanged", user)
})
If the user is logged in, the callback is called once, with the user.
If the user is not logged in, the callback is also called once, with null.
This suggests I could wait until the first callback after navigating to the page to get the real login state before deciding what view to display, for instance. (I originally thought that it would not get called with null, and so I could end up waiting indefinitely)
Question
Would that be idiomatic usage? Does it seem like it will be robust against updates to firebase? Where can I find this discussed in the official documentation?
2022 Edit: in firebase web SDK 9, it's
import { getAuth, onAuthStateChanged } from "firebase/auth";
const auth = getAuth();
export const isReady = new Promise(resolve => {
const unsubscribe = onAuthStateChanged(auth, (/* user */) => {
resolve(/* user */)
unsubscribe()
})
})
P.S: The reason I don't resolve with the user is because it is available at auth.currentUser, while the promise would retain an outdated value.
Looking at similar questions such as Pattern for Firebase onAuthStateChanged and Navigation Guards - Quasar app it seems this is indeed the way it's done.
So I have come up with the following to differentiate the initial condition:
export const isReady = new Promise(resolve => {
const unsubscribe = firebase.auth().onAuthStateChanged(() => {
resolve()
unsubscribe()
})
})
I export this Promise from the module where I wrap firebase, so I can begin other initialization while waiting for an authoritative authentication state.
this worked for me instead. NB: For those user Quasar
export default async ({ app, router, store }) => {
return new Promise(resolve => {
const unsubscribe = auth.onAuthStateChanged((user) => {
auth.authUser = user
resolve()
unsubscribe()
})
})
}