Firebase Anonymous User Authentication flow - javascript

I have the following js code where I try to login on the blog_root object and then use my user_root to listen for any changes with user_root.on("child_added",...)
var root = new Firebase( /*my firebase url*/);
var blog_root = root.child("blog");
var user_root;
console.log("====");
blog_root.authAnonymously(function(err, authData) {
if(err){
console.log("user not authenticated?"+err);
}else{
console.log("user authenticated?"+authData.uid);
if(authData){
user_root = blog_root.child(authData.uid);
user_root.set(authData);
}
}
},{
remember: "sessionOnly"
});
user_root.on(
"child_added", //event_name
function(snapshot) {
//called for each existing element and each new element
}
Using this code specifically my user_root object is shown to be undefined. I check my data and the anonymous user has been successfully added to the blog_root, but I cannot seem to be able to listen to changes on user_root.
What I gather is that the user_root.on("child_added") event is fired on my authentication statement, but this makes no sense to me because user_root is initialized inside the authentication statement.
Any clues?

The authAnonymously method is asynchronous, and the callback is invoked after you first get to user_root.on('child_added', ...). Try moving this listener into the callback from authAnonymously.
Also, note that each time you call authAnonymously(), you're creating a new session. Sessions are automatically persisted by default. To check whether or not the user is already authenticated, try ref.getAuth(), and only invoke authAnonymously() if the user is unauthenticated (ref.getAuth() === null).

Related

onAuthStateChanged being called too early

I'm tyring to create a new user with some additional data (eg: subscribeToEmail field etc.). From what I have read online, the way to go about doing this is to authenticate a user (eg: by using createUserWithEmailAndPassword), and then using the uid that I obtain from that to create a new document in a users collection. That is what I'm trying to do below:
const handleSignup = async (formData) => {
const {email, password, ...otherUserData} = formData;
const {user} = await auth.createUserWithEmailAndPassword(email, password);
console.log("generating user doc at signup...");
await generateUserDocument(user, otherUserData); // creates user doc (called 2nd)
}
The generateUserDocument will create a user document (if it doesn't already exist) in the users collection using the uid of the user obtained from the createUserWithEmailAndPassword function call. I also have also set up an auth state change event handler:
auth.onAuthStateChanged(async (userAuth) => { // get the current user
if (userAuth) {
const user = await generateUserDocument(userAuth); // user (should) already exists, so generateUserDocument will fetch the user rather than creating a new user doc, but this doesn't happen, as it is called 1st and not 2nd
dispatch(login(user)); // login logic
} else {
dispatch(logout());
}
});
The issue here is, when I call createUserWithEmailAndPassword the onAuthStateChanged callback triggers, which then calls generateUserDocument before I have actually created a user document with generateUserDocument(user, otherUserData); inside of the handleSignup method. In other words, the fetch user method: generateUserDocument inside of .onAuthStateChange() is being invoked before the user is actually created, which is done by the generateUserDocument inside of the handleSignup method. As a result, the user data I'm fetching inside of authStateChange doesn't include the details I'm after.
Is there a way to fix this so that my function call after the auth.createuserWithEmailAndPassword() is called before the onAuthStateChange event handler is executed (rather than after)? I have thaught about using something like .onSnapshot() perhaps, but I'm thinking that this might be a little overkill as the user data table shouldn't really need to be continously be listened too, as it will rarely changed. Preferably there is a lifecycle method that gets invoked before onAuthStateChanged that I could use to populate my users collection, but I haven't been able to find much on that.
For reference, I have been following this article regarding associating additional user data with a auth-user record.
Is there a way to fix this so that my function call after the
auth.createuserWithEmailAndPassword() is called before the
onAuthStateChange event handler is executed (rather than after)?
No, there is no out-of-the-box way, because
On successful creation of the user account with createuserWithEmailAndPassword() the user will also be signed in to your application (see the doc), and
The onAuthStateChange() observer is triggered on sign-in or sign-out.
So you indeed need to wait the user Firestore doc is created before proceeding. In my opinion, the best approach is the one you have mentioned, i.e. setting a listener to the user document.
You can do that in such a way you cancel the listener right after the first time you get the data from the user doc, as shown below. This way the user doc is not continuously being listened to.
auth.onAuthStateChanged((userAuth) => { // No more need to be async
// get the current user
if (userAuth) {
const userDocRef = firestore.collection('users').doc(userAuth.uid);
const listener = userDocRef.onSnapshot((doc) => {
if (doc.exists) {
console.log(doc.data());
// Do any other action you need with the user's doc data
listener(); // Calling the unsubscribe function that cancels the listener
dispatch(login(user));
}
});
} else {
dispatch(logout());
}
});
Another approach could be to use a Callable Cloud Function which, in the backend, creates the user in the Auth service AND the document in Firestore. Your handleSignup function would be as follows:
const handleSignup = async (formData) => {
const createUser = firebase.functions().httpsCallable('createUser');
await createUser({formData});
// The user is created in the Auth service
// and the user doc is created in Firestore
// We then need to signin the user, since the call to the Cloud Function did not do it!
const {email, password, ...otherUserData} = formData;
await auth.signInWithEmailAndPassword(email, password);
// The onAuthStateChanged listener is triggered and the Firestore doc does exist
}

What is preferred way to show DOM elements conditioned on firebase authentication

I'm trying to build a small web-page where sign-in is controlled by Firebase Google Auth and is popped up with profile page. What is the secured and preferred way to show the profile page?
Currently I am using onAuthStateChanged to manipulate particular div which holds profile data when user is signed-in. If the user is not logged in I am using removeChild() method to remove that div from DOM and when logged in appendChild() adds back the div.
Supposing you're using firebase's native firebase.auth().onAuthStateChanged function
firebase.auth().onAuthStateChanged(function(user) {
if (user) {
// User is signed in.
} else {
// No user is signed in.
}
});
As well as firebase.auth().currentUser to check if the user is currently logged in or not.
In that case, it's perfectly fine to use removeChild and appendChild and they do not hold any security threats, as if a user is not logged, after a page refresh all of the information will vanish.
Here's a small firebase application that shows that when the connection to the firebase is closed and removeChild is used, appendChild stops working as firebase is disconnected, thus proving the point that it's safe to use.
https://jsfiddle.net/vh9xay6e/
Note that in this example I'm not testing any authentification, just the use of firebase with removeChild and appendChild.
You can see that once the connection to Firebase is over, nothing on the frontend side can happen to change that.
Using onAuthStateChanged method we can act upon state change (sign in or Sign out)
As an example :
firebase.auth().onAuthStateChanged(user=>{
if(user){
document.getElementById("id").classList.remove('hide')
//this will populate your class which associate with above id.
} else{
document.getElementById("id_of_your_div").classList.add('hide')
}
})
I think it's okay to use removeChild and appendChild method based on firebase auth state changes in your application.
try to wire around your code by:
var userCurrent = firebase.auth().currentUser; in a function.
NOTE: Make sure you need to be sure first you signed in (as its an async operation), followed by the update user data as:
var authenticationRef = firebase.auth();
authenticationRef.onAuthStateChanged(function(user) {
if (user) {
console.log('onAuthStateChanged : '+user.displayName);
_updateUser();
} else {
console.log('Not login');
}
});
fucntion _updateUser(){
var userCurrent = firebase.auth().currentUser;
userCurrent.updateProfile({
displayName: "Karthik.S",
}).then(function() {
var displayName = userCurrent.displayName;
}, function(error) {
});
}

Firebase synchronisation of locally-modified data: handling errors & global status

I have two related questions regarding the Firebase web platform's
synchronisation of locally-modified data to the server:
Every client sharing a Firebase database maintains its own internal version of any active data.
When data is updated or saved, it is written to this local version of the database.
The Firebase client then synchronizes that data with the Firebase servers and with other clients on a 'best-effort' basis.
1. Handling sync errors
The data-modification methods
(set(),
remove(), etc)
can take an onComplete callback parameter:
A callback function that will be called when synchronization to the Firebase servers
has completed. The callback will be passed an Error object on failure; else null.
var onComplete = function(error) {
if (error) {
console.log('Synchronization failed');
} else {
console.log('Synchronization succeeded');
}
};
fredRef.remove(onComplete);
In the example above, what kind of errors should the fredRef.remove() callback expect to receive?
Temporary errors?
Client is offline (network connection lost) ?
Firebase server is temporarily overloaded or down for maintenance, but will be available again soon?
Permanent errors?
Permission denied (due to security rules) ?
Database location does not exist?
Is there a way to distinguish between temporary and permanent errors?
How should we handle / recover from these errors?
For temporary errors, do we need to call fredRef.remove() again after a short period of time, to retry the operation?
2. Global sync status
I realise that each call to set() and remove() will receive an individual sync success/failure
result in the onComplete callback.  But I'm looking for a way to determine the
global sync status of the whole Firebase client.
I'd like to use a beforeunload event listener
to warn the user when they attempt to leave the page before all modified data has been synced to the server,
and I'm looking for some function like firebase.isAllModifiedDataSynced().  Something like this:
window.addEventListener('beforeunload', function (event) {
if (!firebase.isAllModifiedDataSynced()) {
event.returnValue = 'Some changes have not yet been saved. If you ' +
'leave this page, your changes will be lost.';
}
});
Here's an example of the same functionality in Google Drive:
I'm aware of the special /.info/connected location:
it is useful for a client to know when it is online or offline.
Firebase clients provide a special location at /.info/connected which is updated every time the client's connection state changes.
Here is an example:
var connectedRef = new Firebase("https://<YOUR-FIREBASE-APP>.firebaseio.com/.info/connected");
connectedRef.on("value", function(snap) {
if (snap.val() === true) {
alert("connected");
} else {
alert("not connected");
}
});
The special /.info/connected location can be connected to a beforeunload event listener like this:
var connectedRef = new Firebase('https://myapp.firebaseio.com/.info/connected');
var isConnected = true;
connectedRef.on('value', function (snap) {
isConnected = snap.val();
});
window.addEventListener('beforeunload', function (event) {
if (!isConnected) {
event.returnValue = 'Some changes have not yet been saved. If you ' +
'leave this page, your changes will be lost.';
}
});
My question is:
If isConnected is true, does this also mean that all modified data has been synced to the server?
i.e. Does "connected" also mean "synced"?
If not, how can the app determine the global sync status of the whole Firebase client?
Is there a special /.info/synchronized location?
Does the app need to manually keep track of the sync success/failure result of every onComplete callback?
In the example above, what kind of errors should the fredRef.remove() callback expect to receive?
Client is offline (network connection lost) ?
No, this will not cause an error to be passed to the completion listener. It will simply cause the completion listener to not be called (yet).
Firebase server is temporarily overloaded or down for maintenance, but will be available again soon?
No. This is essentially the same as being without a network connection.
Permission denied (due to security rules) ?
Yes, this is will indeed cause an error to be passed to the completion handler.
Database location does not exist?
No, this will not cause an error to be caused to the completion listener.
If isConnected is true, does this also mean that all modified data has been synced to the server? i.e. Does "connected" also mean "synced"?
No it does not. .info/connected will fire with true when a connection is made to the database.
If not, how can the app determine the global sync status of the whole Firebase client?
There is currently no way to determine whether your local data is up to date with the server.
Is there a special /.info/synchronized location?
No, such a location doesn't exist.
Does the app need to manually keep track of the sync success/failure result of every onComplete callback?
That depends on the use-case. But if you want to simply know when all your writes are executed, push a dummy value and wait for that to complete. Since Firebase executes the writes in order, you can be certain at that stage that you've gotten the other events.

Picking up meteor.js user logout

Is there any way to pick up when a user logs out of the website? I need to do some clean up when they do so. Using the built-in meteor.js user accounts.
I'll be doing some validation using it, so I need a solution that cannot be trigger on behalf of other users on the client side - preferably something completely server side.
You may use Deps.autorun to setup a custom handler observing Meteor.userId() reactive variable changes.
Meteor.userId() (and Meteor.user()) are reactive variables returning respectively the currently logged in userId (null if none) and the corresponding user document (record) in the Meteor.users collection.
As a consequence one can track signing in/out of a Meteor application by reacting to the modification of those reactive data sources.
client/main.js :
var lastUser=null;
Meteor.startup(function(){
Deps.autorun(function(){
var userId=Meteor.userId();
if(userId){
console.log(userId+" connected");
// do something with Meteor.user()
}
else if(lastUser){
console.log(lastUser._id+" disconnected");
// can't use Meteor.user() anymore
// do something with lastUser (read-only !)
Meteor.call("userDisconnected",lastUser._id);
}
lastUser=Meteor.user();
});
});
In this code sample, I'm setting up a source file local variable (lastUser) to keep track of the last user that was logged in the application.
Then in Meteor.startup, I use Deps.autorun to setup a reactive context (code that will get re-executed whenever one of the reactive data sources accessed is modified).
This reactive context tracks Meteor.userId() variation and reacts accordingly.
In the deconnection code, you can't use Meteor.user() but if you want to access the last user document you can use the lastUser variable.
You can call a server method with the lastUser._id as argument if you want to modify the document after logging out.
server/server.js
Meteor.methods({
userDisconnected:function(userId){
check(userId,String);
var user=Meteor.users.findOne(userId);
// do something with user (read-write)
}
});
Be aware though that malicious clients can call this server method with anyone userId, so you shouldn't do anything critical unless you setup some verification code.
Use the user-status package that I've created: https://github.com/mizzao/meteor-user-status. This is completely server-side.
See the docs for usage, but you can attach an event handler to a session logout:
UserStatus.events.on "connectionLogout", (fields) ->
console.log(fields.userId + " with connection " + fields.connectionId + " logged out")
Note that a user can be logged in from different places at once with multiple sessions. This smart package detects all of them as well as whether the user is online at all. For more information or to implement your own method, check out the code.
Currently the package doesn't distinguish between browser window closes and logouts, and treats them as the same.
We had a similar, though not exact requirement. We wanted to do a bit of clean up on the client when they signed out. We did it by hijacking Meteor.logout:
if (Meteor.isClient) {
var _logout = Meteor.logout;
Meteor.logout = function customLogout() {
// Do your thing here
_logout.apply(Meteor, arguments);
}
}
The answer provided by #saimeunt looks about right, but it is a bit fluffy for what I needed. Instead I went with a very simple approach like this:
if (Meteor.isClient) {
Deps.autorun(function () {
if(!Meteor.userId())
{
Session.set('store', null);
}
});
}
This is however triggered during a page load if the user has not yet logged in, which might be undesirable. So you could go with something like this instead:
if (Meteor.isClient) {
var userWasLoggedIn = false;
Deps.autorun(function (c) {
if(!Meteor.userId())
{
if(userWasLoggedIn)
{
console.log('Clean up');
Session.set('store', null);
}
}
else
{
userWasLoggedIn = true;
}
});
}
None of the solutions worked for me, since they all suffered from the problem of not being able to distinguish between manual logout by the user vs. browser page reload/close.
I'm now going with a hack, but at least it works (as long as you don't provide any other means of logging out than the default accounts-ui buttons):
Template._loginButtons.events({
'click #login-buttons-logout': function(ev) {
console.log("manual log out");
// do stuff
}
});
You can use the following Meteor.logout - http://docs.meteor.com/#meteor_logout

How to prevent auto login after create user

I add accounts-password and accounts-base packages in Meteor
When I create user like this:
Accounts.createUser({username: username, password : password}, function(err){
if (err) {
// Inform the user that account creation failed
console.log("Register Fail!")
console.log(err)
} else {
console.log("Register Success!")
// Account has been created and the user has logged
}
});
Account has been created and the user has logged.
for instance, I log in as an administrator and I want to create a account for somebody,but I don't want to log out after create account.
How to prevent auto login after create user ?
I find source code of accouts-password packages:
48 - 63 lines:
// Attempt to log in as a new user.
Accounts.createUser = function (options, callback) {
options = _.clone(options); // we'll be modifying options
if (!options.password)
throw new Error("Must set options.password");
var verifier = Meteor._srp.generateVerifier(options.password);
// strip old password, replacing with the verifier object
delete options.password;
options.srp = verifier;
Accounts.callLoginMethod({
methodName: 'createUser',
methodArguments: [options],
userCallback: callback
});
};
Should I modify the source code to solve this problem?
Any help is appreciated.
You're trying to use client side accounts management to perform a task it hasn't been designed for.
Client side accounts package purpose is to specifically allow new users to create their account and expect to be logged in immediately.
You have to remember that certain functions can be ran on the client and/or on the server with different behaviors, Accounts.createUser docs specifies that : "On the client, this function logs in as the newly created user on successful completion."
On the contrary, "On the server, it returns the newly created user id." (it doesn't mess with the currently logged in user on the client).
In order to solve your problem, you should write a server side method creating a new user and be able to call it from your client side admin panel, after filling correctly a user creation form of your own design.
If you really want this behavior you would need to modify password_server.js
and remove lines 474-475 containing:
// client gets logged in as the new user afterwards.
this.setUserId(result.id);
So the User would not be logged in after the user is created.
I had the same problem. I wanted to create an admin interface where an administrator can set a user's password but not pass it to a server method in plaintext. The client side of Accounts.createUser already deals with this, so I just alter the normal sequence of events in accounts-password/password-server.js in the presence of a flag. Its not perfect or pretty but seems to work and you don't have to modify the accounts-password package directly.
Meteor.startup(function ()
{
// store the default createUser method handler
var default_create_user = Meteor.server.method_handlers.createUser;
// remove it so we can register our own
delete Meteor.server.method_handlers.createUser;
Meteor.methods({createUser: function (options) {
var default_login_method = Accounts._loginMethod;
// check if noAutoLogin flag is present
if (options.noAutoLogin)
{
// temporarily disable the login method while creating our user
// NB: it might be possible that simultaneous calls to createUser that do want the default behavior
// would use the altered Accounts._loginMethod instead
Accounts._loginMethod = function(s, m, a, p, fn)
{
// this is the callback that is constructed with a closure of the options array and calls internal create functions
fn();
// restore default _loginMethod so other calls are not affected
Accounts._loginMethod = default_login_method;
}
}
// invoke the default create user now that the login behavior has been short-circuited
default_create_user(options);
}});
});
If you want to continue using Accounts.createUser on the client without logging the user in. You can call Meteor.logout() from createUser's optional callback.
Accounts.createUser(user, err => {
if (err) {
// handle error
return;
}
// Prevent unwanted login
Meteor.logout();
});

Categories

Resources