firebase.initializeApp() does not reestablish a connection to onAuthStateChanged - javascript

I have three firebase instances: dev, staging and prod.
My application needs to authenticate the user with an email and password.
At login time my application has no idea if the user is in the dev, staging or prod databases.
So I decided to loop over my different config files and attempt to login to each database one-by-one. The logic in pseudocode looks like this:
var success = reinitializeFirebaseAndLogin(prodConfig, email, password);
if (!success) {
success = reinitializeFirebaseAndLogin(stagingConfig, email, password);
if (!success) {
success = reinitializeFirebaseAndLogin(devConfig, email, password);
if (!success) {
display("Login Failed.");
}
}
}
function reinitializeFirebaseAndLogin(config, email, password) {
reinitializeDefaultFirebase(config);
firebase.auth().signInWithEmailAndPassword(email, password); //if successful, onAuthStateChanged() should be called.
}
// http://stackoverflow.com/questions/37582860/firebase-v3-how-to-reinitialize-default-app
function reinitializeDefaultFirebase(config) {
firebase.app().delete().then(function() {
firebase.initializeApp(config);
});
}
The problem is once the reinitializeDefaultFirebase() method is called then onAuthStateChanged is no longer called.
It seems that the firebase.app().delete() loses the connection to firebase.auth().onAuthStateChanged().
So the questions become:
How do I reestablish the connection to onAuthStateChanged?
Is this logic correct or is there a better way?
Should firebase.initializeApp() reestablish a connection to onAuthStateChanged without any special coding on my side?
Thank you for your time.

Related

Firebase: Email verification link always expired even though verification works

I'm trying to set up an email verification flow in my project, but I can't seem to get it right.
How my flow works now is the user enters their credentials (email and password), which are used to create a new firebase user. Then, once that promise is resolved, it sends an email verification link to the new user that was created. The code looks like this:
async createUser(email: string, password: string) {
try {
console.log("Creating user...");
const userCredentials = await createUserWithEmailAndPassword(
auth,
email,
password
);
console.log("Successfully created user");
const { user } = userCredentials;
console.log("Sending email verification link...");
await this.verifyEmail(user);
console.log("EMAIL VERIFICATION LINK SUCCESSFULLY SENT");
return user;
} catch (err) {
throw err;
}
}
async verifyEmail(user: User) {
try {
sendEmailVerification(user);
} catch (err) {
throw err;
}
}
The link is sent through fine, but once I press on it, I'm redirected to a page that says this:
Strangely, the user's email is verified after this, in spite of the error message displayed. Any idea why this is happening?
Update:
I managed to figure it out. The email provider I'm using is my university's, and it seems to be preventing the verification link from working properly. I did try with my personal email to see if that was the case, but I wasn't seeing the verification link appearing there. I eventually realized that it was because it was being stored in the spam folder. It's working on other email providers, though, ideally, I'd want it to work on my university's email provider (the emails that users sign up with are supposed to be exclusively student emails). Any ideas how I could resolve this?
I eventually figured out that the issue was with my email provider. I was using my student email, which the university provides, and I imagine they've placed rigorous measures in place to secure them as much as possible. I have no idea what was preventing it from working, but I managed to figure out a workaround.
In brief, I changed the action URL in the template (which can be found in the console for your Firebase project in the Authentication section, under the Templates tab) to a route on my website titled /authenticate. I created a module to handle email verification. Included in it is a function that parses the URL, extracting the mode (email verification, password reset, etc.), actionCode (this is the important one. It stores the id that Firebase decodes to determine if it's valid), continueURL (optional), and lang (optional).
export const parseUrl = (queryString: string) => {
const urlParams = new URLSearchParams(window.location.search);
const mode = urlParams.get("mode");
const actionCode = urlParams.get("oobCode");
const continueUrl = urlParams.get("continueUrl");
const lang = urlParams.get("lang") ?? "en";
return { mode, actionCode, continueUrl, lang };
};
I created another method that handles the email verification by applying the actionCode from the URL using Firebase's applyActionCode.
export const handleVerifyEmail = async (
actionCode: string,
continueUrl?: string,
lang?: string
) => {
try {
await applyActionCode(auth, actionCode);
return { alreadyVerified: false };
} catch (err) {
if (err instanceof FirebaseError) {
switch (err.code) {
case "auth/invalid-action-code": {
return { alreadyVerified: true };
}
}
}
throw err;
}
};
The auth/invalid-action-code error seems to be thrown when the user is already verified. I don't throw an error for it, because I handle this differently to other errors.
Once the user presses the verification link, they're redirected to the /authenticate page on my website. This page then handles the email verification by parsing the query appended to the route. The URL looks something like this http://localhost:3000/authenticate?mode=verifyEmail&oobCode=FLVl85S-ZI13_am0uwWeb4Jy8DUWC3E6kIiwN2LLFpUAAAGDUJHSwA&apiKey=AIzaSyA_V9nKEZeoTOECWaD7UXuzqCzcptmmHQI&lang=en
Of course, in production, the root path would be the name of the website instead of localhost. I have my development environment running on port 3000.
Once the user lands on the authentication page, I handle the email verification in a useEffect() hook (Note: I'm using Next.js, so if you're using a different framework you might have to handle changing the URL differently):
useEffect(() => {
verifyEmail();
async function verifyEmail() {
const { actionCode } = parseUrl(window.location.search);
if (!actionCode) return;
router.replace("/authenticate", undefined, { shallow: true });
setLoadingState(LoadingState.LOADING);
try {
const response = await handleVerifyEmail(actionCode!);
if (response.alreadyVerified) {
setEmailAlreadyVerified(true);
onEmailAlreadyVerified();
return;
}
setLoadingState(LoadingState.SUCCESS);
onSuccess();
} catch (err) {
console.error(err);
onFailure();
setLoadingState(LoadingState.ERROR);
}
}
}, []);
It first checks if there is an action code in the URL, in case a user tries to access the page manually.
The onSuccess, onFailure, and onEmailAlreadyVerified callbacks just display toasts. loadingState and emailAlreadyVerified are used to conditionally render different responses to the user.

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

Firebase 4.1.3: onAuthStateChange not called after first sign in with email and password

I upgraded the version of Firebase for my app from 3.5.0 to 4.1.3 and noticed that the onAuthStateChange callback function is no longer called after a user successfully signs in for the first time after verifying their email address.
The app is written in JavaScript.
These are the relevant sections of my code:
Callback setup
firebase.auth().onAuthStateChanged(onAuthStateChange);
Callback
function onAuthStateChange (user) {
console.log(user); // Not appearing in console
}
Sign in
firebase.auth().signInWithEmailAndPassword(email, password)
.then(function (user) {
console.log("signInWithEmailAndPassword success"); // This appears in the console
})
.catch(function(error) {
console.log("signInWithEmailAndPassword", error);
self.signInError = error.message;
$timeout(function () {
$scope.$apply();
});
});
Edit - these are the steps to reproduce the problem (the typical action of a user of my app):
User downloads and launches app
User registers with email and password
App sends email verification email
User receives verification email and clicks on link
User goes to sign in page of app
User signs in triggering the console.log("signInWithEmailAndPassword success");
onAuthStateChanged callback is not called
For development and testing purposes (not what a user would do but I have done)
User reloads the app
User is now in the app
User signs out of the app
User signs in to the app triggering the console.log("signInWithEmailAndPassword success");
onAuthStateChanged callback is called
The problem is that auth().createUserWithEmailAndPassword() does in fact log in a type of user in.
You will find that if you type
firebase.auth().onAuthStateChanged(user => {
console.log("onAuthStateChanged()");
that createUserWithEmailAndPassword() does trigger the console log. However, there seems to be no valid "user" object, which would explain why nothing appears for you since you are only logging the user.
I ran into the exact same problems. At the sendEmailverification() step notice how it does require you to use auth().currentUser, signalling there must be some sort of user signed in (I am not sure how firebase handles the difference between email verified users and non-verified users behind the scenes)
You can simply called the signOut() function after sending the email verification and it should allow the onAuthStateChanged() function to call when logging in for the first time (without reloading the app)
firebase.auth().currentUser.sendEmailVerification()
.then(() => {
console.log("Email verification sent");
firebase.auth().signOut().then(() => {
console.log("Signed out!");
}).catch((err) => {
console.log("Error signing out!", err);
});
It is rather confusing that you can actually "Log in" successfully without causing a change in AuthStateChanged or returning any errors.
TLDR: Remember to use the auth().signOut() function after sending the email verification.
Try this way, i hope it'll work
firebase.auth().onAuthStateChanged(function(user){
console.log(user);
})

How to handle Quickblox session expiration?

In quickblox, the session expires two hours after the last request. So to handle this situation I have used the code
config.on.sessionExpired = function(next,retry){
})
and passed the config in QB.init
config.on.sessionExpired = function(next, retry) {
console.log("============session renewal")
var user = self.get('user')
QB.chat.disconnect()
QB.createSession({ login: user.login, password: user.pass }, function(err, res) {
if (res) {
// save session token
token = res.token;
user.id = res.user_id
self.get('user').token = token
QB.chat.connect({ userId: user.id, password: user.pass }, function(err, roster) {
// Do something
})
}
})
}
QB.init(QBApp.appId, QBApp.authKey, QBApp.authSecret, config);
Is this the right way to renew the session by first disconnecting the chat, then creating a new session first and then connecting the chat back again?
I do not want the client to know that the session has expired in quickblox and they have to refresh the page. The chat is a part of the web portal. It is fine if the quickblox takes 2-3 seconds to create a new session token and then connect to chat. By the time, I can show a loader or some message.
I had tried it without the QB.chat.disconnect() but then it did not work and sent me Unprocessable entity 422 error.
I have same problem, and i found some solution at QuickBlox Docs
https://docs.quickblox.com/docs/js-authentication#session-expiration
QuickBlox JavaScript SDK will automatically renew your current session. There is no need to manually call createSession() method. After login, a session is available for 2 hours. When the session expires, any request method will firstly renew it and then execute itself.
And this example from the official documentation:
var CONFIG = {
on: {
sessionExpired: function(handleResponse, retry) {
// call handleResponse() if you do not want to process a session expiration,
// so an error will be returned to origin request
// handleResponse();
QB.createSession(function(error, session) {
retry(session);
});
}
}
};
QB.init(3477, "ChRnwEJ3WzxH9O4", "AS546kpUQ2tfbvv", config);

Meteor's createUser running on client and server

I'm fairly new to Meteor and trying to grasp its concepts. I have a client code below that triggers Meteor method to create new user:
Template["signup-team"].onRendered(function(){
var validator = $('.signup-team-form').validate({
submitHandler: function(event){
var email = $('[name=email]').val();
var password = $('[name=password]').val();
Meteor.call('addNewUser', email, password, "team-captain", function(error, result) {
if (error){
return alert(error.reason);
}
Router.go("complete-signup");
});
}
});
});
The method is defined to run on both client and server. When run on the server I want it to create user and add role to account. On the client side I want to sign user in.
Meteor.methods({
addNewUser: function(email, password, role) {
check(email, String);
check(password, String);
if(Meteor.isClient){
Accounts.createUser({
email: email,
password: password,
profile: {
completed: false
}
}, function(error){
if(error){
console.log(error); // Output error if registration fails
} else {
console.log(Meteor.userId());
}
});
} else {
var id = Accounts.createUser({
email: email,
password: password,
profile: {
completed: false
}
});
console.log(id);
Roles.addUsersToRoles(id, role);
}
}
});
The server part runs fine and new user is created but on client side I get error Error: No result from call to createUser and user isn't signed in automatically.
I assume the problem is I dont need to run createUser on the client and use Meteor.loginWithPassword instead but I would really like to know the theory behind this. Thanks
Don't do this. You are rewriting core code and creating security issues needlessly.
Instead of using your addNewUser method, just call Accounts.createUser on the client. Have a onCreateUser callback handle adding the role.
In your code, you are sending the users password to the server in plaintext. When you call Accounts.createUser, the password is hashed before being sent to the server. It also takes care of logging in the new user for you.
One gotcha with adding the role though, you will not be able to use Roles.addUsersToRoles(id, role) in the onCreateUser callback, as the user object has not yet been added to the database, and does not have an _id. However you can directly add the role to the proposed user object like this:
Accounts.onCreateUser(function(options, user) {
user.roles = ['team-captain']
return user;
})
Then again, maybe you don't want all users to be team captains!

Categories

Resources