How can I log in a user right after his/her email has been verified using firebase/auth and react-native without creating a whole landing page? - firebase

Notice: I have seen this question, but creating a whole landing page just to verify a user seems a bit much.
I added a login functionality to my react-native app using firebase/auth with email and password. This works well so far and I have no issues doing that.
I then continued to send a verification email to a new user and only allow him/her to use the app, once the email is verified. Again, no issues here.
The next step would be to login the user right after the email was verified. This is where I'm stuck, since the onAuthStateChanged eventhandler doesn't update after the user pressed the verification link in the email.
Is there any way to listen to the emailVerified state in real-time? I tried to use polling with setInterval() but this is not great since there is a notable delay between verification and login. I read about a continueLink you can pass to sendEmailVerification, but I couldn't figure out how to make that work in react-native.
I'm using Expo and therefore the Firebase SDK, not the Firebase react native package.
Here is the code I use for the signup:
export const signUp = async (username: string, email: string, password: string) => {
try {
const auth = getAuth();
if (email && password && username) {
// sign up
const userCredential = await createUserWithEmailAndPassword(auth, email, password);
// save username in firestore
await setUserName(userCredential, username);
// send Email Verification
await sendEmailVerification(userCredential.user);
return true;
}
} catch (error) {
onError(error);
}
};
And this is my onAuthStateChanged handler:
auth.onAuthStateChanged(authenticatedUser => {
try {
if (authenticatedUser?.emailVerified) {
setUser(authenticatedUser)
} else {
setUser(null)
}
} catch (error) {
console.log(error);
}
});

So in the end I did follow this question, but I changed it a bit to fit my needs. I'll post my steps for anyone who's doing the same.
Create a simple static website with firebase init and host it on firebase or somewhere else (check the hosting tab in your firebase console to get started)
Follow this guide to create the appropriate handlers on the website
Add the following to your verificationHandler to update the user (don't forget to import firestore) (I send the userId via the continueURL, but there are probably better ways)
// You can also use realtime database if you want
firebase.firestore().collection("users").doc(userId).set({
emailVerified: true
}, {merge: true}).then(() => {
message.textContent = "Your email has been verified.";
}).catch((error) => {
message.textContent = "The verification was invalid or is expired. Please try to send another verification email from within the app.";
});
Got to authentication -> templates in your firebase console and change the action url to your hosted website's url
Add a listener to the firestore doc to your react-native app
const onUserDataChanged = (uid, callback) => {
onSnapshot(doc(firestore, "users", uid), doc => callback(doc.data()));
}
Use the data from the callback to update the login state in the app
// As an example
auth.onAuthStateChanged(authenticatedUser => {
if (authenticatedUser && !authenticatedUser.emailVerified) {
unsubscribeFirestoreListener?.();
unsubscribeFirestoreListener = onUserDataChanged(authenticatedUser.uid, (data: any) => {
if (data?.emailVerified) {
setUser(authenticatedUser);
unsubscribeFirestoreListener?.();
}
});
}
}

use the codes below for your authentication context. for user id, you should use 'user.uid'
import React, { useState, createContext } from "react";
import * as firebase from "firebase";
import { loginRequest } from "./authentication.service";
export const AuthenticationContext = createContext();
export const AuthenticationContextProvider = ({ children }) => {
const [isLoading, setIsLoading] = useState(false);
const [user, setUser] = useState(null);
const [error, setError] = useState(null);
firebase.auth().onAuthStateChanged((usr) => {
if (usr) {
setUser(usr);
setIsLoading(false);
} else {
setIsLoading(false);
}
});
const onLogin = (email, password) => {
setIsLoading(true);
firebase.auth().signInWithEmailAndPassword(email, password)
.then((u) => {
setUser(u);
setIsLoading(false);
})
.catch((e) => {
setIsLoading(false);
setError(e.toString());
});
};
const onRegister = (email, password, repeatedPassword) => {
setIsLoading(true);
if (password !== repeatedPassword) {
setError("Error: Passwords do not match");
return;
}
firebase
.auth()
.createUserWithEmailAndPassword(email, password)
.then((u) => {
setUser(u);
setIsLoading(false);
})
.catch((e) => {
setIsLoading(false);
setError(e.toString());
});
};
const onLogout = () => {
setUser(null);
firebase.auth().signOut();
};
return (
<AuthenticationContext.Provider
value={{
isAuthenticated: !!user,
user,
isLoading,
error,
onLogin,
onRegister,
onLogout,
}}
>
{children}
</AuthenticationContext.Provider>
);
};

Related

Firebase Auth: How to unsubscribe from Auth observer after user creation and then subscribe again?

I am using the createUserWithEmailAndPassword() method for signing up new users. Immediately after this user creation process, I am sending an email verification. Then, in my onAuthStateChanged() I have a condition to check whether the user has verified their email. The problem is that the Auth observer is logging out the user BEFORE the email sendEmailVerification() method is complete.
Based on the below code, where is the best place to succuessfully unsubscribe the observer ? And, how to do it with Firebase JS SDK v9?
Let me explain my use case and show my code:
pages/sign-up:
async signUp() {
const auth = getAuth()
const batch = writeBatch(db)
try {
const UserCredential = await createUserWithEmailAndPassword(
auth,
this.formValues.email,
this.formValues.password
)
const userDocRef = doc(db, 'users', UserCredential.user.uid)
batch.set(userDocRef, {
uid: UserCredential.user.uid,
displayName: this.formValues.displayName,
photoURL: `https://gravatar.com/avatar/${md5(
this.formValues.email
)}?d=identicon`
})
const usernameDocRef = doc(db, 'usernames', this.formValues.displayName)
batch.set(usernameDocRef, { uid: UserCredential.user.uid })
// Commit batch
await batch.commit()
console.log('batch committed, user is:', UserCredential.user.uid)
await this.verifyEmail() // <-- user is logged out before this has a chance to fire!
verifyEmail():
async verifyEmail() {
const auth = getAuth()
const actionCodeSettings = {
url: `${this.$config.baseUrl}/email-confirmation/success`
}
try {
await sendEmailVerification(auth.currentUser, actionCodeSettings)
} catch (error) {
console.error('An email verification error happened', error)
this.errorMessage = error.message
}
},
In my onAuthStateChanged() method, I am immediately logging out the user IF their email is not yet verified. This causes the following error:
And here is how I have my onAuthStateChanged observer set up (it runs before the page is rendered):
~/plugins/auth.js:
onAuthStateChanged(auth, (user) => {
if (user) {
if (!user.emailVerified) {
// User has not verified the email yet
store.dispatch('logOutUser')
}
// TO DO: finish up rest of user logic
Should the unsubscribe be in the auth.js or the pages/sign-up page? I am unsure how to unsubscribe.
If you need to perform certain actions after signup/login, then you should unsubscribe from auth observer as you've figured out.
const authObserver = onAuthStateChanged(auth, (user) => {
// ...
}
async signUp() {
//unsubscribe here i.e when user clicks signup button
authObserver()
const auth = getAuth()
const batch = writeBatch(db)
// ...
}
Do note that, if you you auth observer is meant to redirect logged in user somewhere else then it won't do it now. So make sure you do that manually.

Google Sign In Page Not Display After Second Login React native

I develop application by react native and using rnfirebase to connect with google firebase authentication. I have sign in code by google button like this.
const _signIn = async () => {
setInitializing(true);
try {
await GoogleSignin.hasPlayServices();
const userInfo = await GoogleSignin.signIn();
const credential = auth.GoogleAuthProvider.credential(
userInfo.idToken,
userInfo.accessToken,
);
return auth()
.signInWithCredential(credential)
.then(response => {
const uid = response.user.uid;
const data = {
uid: uid,
email: userInfo.user.email,
fullname: userInfo.user.name,
bio: 'I am ok ..',
username: uid.substring(0, 8),
};
const usersRef = firestore().collection('users');
usersRef
.doc(uid)
.get()
.then(firestoreDocument => {
if (!firestoreDocument.exists) {
usersRef
.doc(data.uid)
.set(data)
.then(() => {
RNRestart.Restart();
})
.catch(error => {
setInitializing(false);
Alert.alert(JSON.stringify(error.message));
});
} else {
setInitializing(false);
}
})
.catch(error => {
Alert.alert(JSON.stringify(error.message));
console.log('Error getting document:', error);
});
});
} catch (error) {
if (error.code === statusCodes.SIGN_IN_CANCELLED) {
setInitializing(false);
Alert.alert('Sign in canceled');
} else if (error.code === statusCodes.IN_PROGRESS) {
setInitializing(false);
Alert.alert('Signin in progress');
} else if (error.code === statusCodes.PLAY_SERVICES_NOT_AVAILABLE) {
setInitializing(false);
Alert.alert('PLAY_SERVICES_NOT_AVAILABLE');
} else {
setInitializing(false);
Alert.alert(JSON.stringify(error.message));
}
} };
In IOS, when i signin as new / registered user, application always display google signin page. So I Can choose which account that I want to use. Like this:
But In Android, Google Signin page Only show for the first time user signin. After that, if user logout from application, and he want to login again by google button, user directly go to main application with last gmail. So In android, I can not switch / use another account if I have sign in before.
How can I show google sign in page every time user sign in by google button in android?
Thankyou.
In your logout function, if the user is logged in through Google, you need to implement the google sign out function:
GoogleSignin.signOut();
A better approach is to also revoke the access, so the complete sign out function could be:
await GoogleSignin.revokeAccess();
await GoogleSignin.signOut();

Simple email verification in React Native and Firebase

I made a simple registration form in React Native. I want to check if the email is valid or not by sending a verification code to it. I read many similar questions but I couldn't combine them with my code because I'm new to React Native. Can anyone help me do that without just posting a link for another answer?
Here's my registration code:
export class Register extends Component {
constructor(props) {
super(props);
this.state = {
email: '',
password: '',
name: '',
lastname: ''
}
this.onSignUp = this.onSignUp.bind(this)
}
onSignUp(){
if(
this.state.email != '' &&
this.state.password != '' &&
this.state.name != ''
){
const { email, password, name } = this.state;
firebase.auth().createUserWithEmailAndPassword(email, password)
.then((result) => {
firebase.firestore().collection("Users")
.doc(firebase.auth().currentUser.uid)
.set({
name,
email
})
console.log(result)
})
.catch((error) => {
console.log(error)
})
}
else{
alert("Please fill the empty spaces!");
}
}
render() { form body }
You cannot verify user email using before creating the account by using Client SDK only. If you need to do that, you will have to use Cloud Functions (or your own server) along with a third party email sending service and implement your own logic.
However you can verify the email of user by sending the user a verification email like this:
//new user created
var user = firebase.auth().currentUser;
user.sendEmailVerification().then(function() {
// Email sent.
}).catch(function(error) {
// An error happened.
});
This will send an email to your user containing a verification link. Thereafter you can use emailVerified property to check if user has verified their email to restrict access to data like:
if (user.emailVerified) {
// process
} else {
alert("Please verify your email")
}
In you case, the code would look like:
const { email, password, name } = this.state;
firebase.auth().createUserWithEmailAndPassword(email, password)
.then(async (result) => {
await firebase.firestore().collection("Users")
.doc(firebase.auth().currentUser.uid)
.set({
name,
email
})
await user.sendEmailVerification()
console.log("Verification Email Sent")
})

Handle facebook login with same account used with Google using firebase

I'm working on a react native project and I've came to a part where initially I implemented google sign in my project using react-native-google-signin and later on Facebook sign in using react-native-fbsdk packages with the help of firebase and both worked like a charm "individually".
The Problem
Let's say the user logged in using google account and it worked but later logged in using Facebook with the same account (I'm allowing only one email per user in firebase), I get an error
auth/account-exists-with-different-credentials
I want the user to be able to login using Facebook from the login screen or to be more specific to link his account from the login screen.
What have I tried?
I searched online and found some answers and got up with this solution or piece of code:
facebookSignin: async () => {
const result = await LoginManager.logInWithPermissions([
'public_profile',
'email',
]);
if (result.isCancelled) {
alert('User cancelled the login process');
this.setState({loginInProcess: false});
}
const data = await AccessToken.getCurrentAccessToken();
if (!data) {
alert('Something went wrong obtaining access token');
this.setState({loginInProcess: false});
}
const facebookCredential = auth.FacebookAuthProvider.credential(
data.accessToken,
);
await auth()
.signInWithCredential(facebookCredential)
// The problem starts here from the catch block
.catch((error) => {
if (
error.code === 'auth/account-exists-with-different-credential'
) {
var pendingCred = error.credential;
var email = error.email;
auth()
.fetchSignInMethodsForEmail(email)
.then(async (methods) => {
if (methods[0] === 'google.com') {
const {idToken} = await GoogleSignin.signIn();
const googleCredential = auth.GoogleAuthProvider.credential(
idToken,
);
auth()
.signInWithCredential(googleCredential)
.then((user) => {
user.linkWithCredential(pendingCred);
})
.catch((error) => console.log(error));
}
});
}
});
}
This code implements a function when triggered, if there is no user with the same email, it proceeds normally, however if there is an error (mentioned above), it will grant the user with a list of google accounts that are present in the user phone (google thing) and when he chooses his account (linked with google account) it doesn't work. The email isn't linked.
To be more specific, I would like somehow to not grant the user with all his google accounts but only with the email to be linked var email = error.email; (in the code snippet above) and for the Facebook provider to be linked successfully.
After a little of hard work, I've managed to make it work in react native and I'm gonna leave the answer here for peeps who are facing the same issue. Be ware that I used react-native-prompt-android to ask the user for confirming his password when trying to link with Facebook.
The user tries to sign with Facebook and gets this error:
auth/account-exists-with-different-credentials
This is how I handled it:
.catch((error) => {
// Catching the error
if (
error.code === 'auth/account-exists-with-different-credential'
) {
const _responseInfoCallback = (error, result) => {
if (error) {
alert('Error fetching data: ' + error.toString());
} else {
setEmail(result.email);
}
};
// Getting the email address instead of error.email from Facebook
const profileRequest = new GraphRequest(
'/me?fields=email',
null,
_responseInfoCallback,
);
new GraphRequestManager().addRequest(profileRequest).start();
if (email) {
auth()
.fetchSignInMethodsForEmail(email)
.then(async (methods) => {
// Checking the method
if (methods[0] === 'password') {
// Prompting the user to confirm/input his password for linking
const AsyncAlert = () => {
return new Promise((resolve, reject) => {
prompt(
'Password Confirmation',
'The email address is already linked with password account. Enter your password to process',
[
{
text: 'Cancel',
style: 'cancel',
},
{
text: 'Continue',
onPress: (password) =>
resolve(setPassword(password)),
},
],
{
type: 'secure-text',
cancelable: false,
placeholder: 'Password',
},
);
});
};
// Here the linking goes
await AsyncAlert().then(async () => {
await auth()
.signInWithEmailAndPassword(email, password)
.then(() => {
return auth().currentUser.linkWithCredential(
facebookCredential,
);
})
.catch(() => alert('Something went wrong'));
});
} else if (methods[0] === 'google.com') {
const {idToken} = await GoogleSignin.signIn(email);
const googleCredential = auth.GoogleAuthProvider.credential(
idToken,
);
await auth()
.signInWithCredential(googleCredential)
.then(() => {
return auth().currentUser.linkWithCredential(
facebookCredential,
);
});
}
});
} else {
alert('Something went wrong');
}
}
});

Firebase: How to stop newly created user from getting logged in automatically? [duplicate]

So I have this issue where every time I add a new user account, it kicks out the current user that is already signed in. I read the firebase api and it said that "If the new account was created, the user is signed in automatically" But they never said anything else about avoiding that.
//ADD EMPLOYEES
addEmployees: function(formData){
firebase.auth().createUserWithEmailAndPassword(formData.email, formData.password).then(function(data){
console.log(data);
});
},
I'm the admin and I'm adding accounts into my site. I would like it if I can add an account without being signed out and signed into the new account. Any way i can avoid this?
Update 20161110 - original answer below
Also, check out this answer for a different approach.
Original answer
This is actually possible.
But not directly, the way to do it is to create a second auth reference and use that to create users:
var config = {apiKey: "apiKey",
authDomain: "projectId.firebaseapp.com",
databaseURL: "https://databaseName.firebaseio.com"};
var secondaryApp = firebase.initializeApp(config, "Secondary");
secondaryApp.auth().createUserWithEmailAndPassword(em, pwd).then(function(firebaseUser) {
console.log("User " + firebaseUser.uid + " created successfully!");
//I don't know if the next statement is necessary
secondaryApp.auth().signOut();
});
If you don't specify which firebase connection you use for an operation it will use the first one by default.
Source for multiple app references.
EDIT
For the actual creation of a new user, it doesn't matter that there is nobody or someone else than the admin, authenticated on the second auth reference because for creating an account all you need is the auth reference itself.
The following hasn't been tested but it is something to think about
The thing you do have to think about is writing data to firebase. Common practice is that users can edit/update their own user info so when you use the second auth reference for writing this should work. But if you have something like roles or permissions for that user make sure you write that with the auth reference that has the right permissions. In this case, the main auth is the admin and the second auth is the newly created user.
Update 20161108 - original answer below
Firebase just released its firebase-admin SDK, which allows server-side code for this and other common administrative use-cases. Read the installation instructions and then dive into the documentation on creating users.
original answer
This is currently not possible. Creating an Email+Password user automatically signs that new user in.
I just created a Firebase Function that triggers when a Firestore document is Created (with rules write-only to admin user). Then use admin.auth().createUser() to create the new user properly.
export const createUser = functions.firestore
.document('newUsers/{userId}')
.onCreate(async (snap, context) => {
const userId = context.params.userId;
const newUser = await admin.auth().createUser({
disabled: false,
displayName: snap.get('displayName'),
email: snap.get('email'),
password: snap.get('password'),
phoneNumber: snap.get('phoneNumber')
});
// You can also store the new user in another collection with extra fields
await admin.firestore().collection('users').doc(newUser.uid).set({
uid: newUser.uid,
email: newUser.email,
name: newUser.displayName,
phoneNumber: newUser.phoneNumber,
otherfield: snap.get('otherfield'),
anotherfield: snap.get('anotherfield')
});
// Delete the temp document
return admin.firestore().collection('newUsers').doc(userId).delete();
});
You can Algo use functions.https.onCall()
exports.createUser= functions.https.onCall((data, context) => {
const uid = context.auth.uid; // Authorize as you want
// ... do the same logic as above
});
calling it.
const createUser = firebase.functions().httpsCallable('createUser');
createUser({userData: data}).then(result => {
// success or error handling
});
Swift 5: Simple Solution
First store the current user in a variable called originalUser
let originalUser = Auth.auth().currentUser
Then, in the completion handler of creating a new user, use the updateCurrentUser method to restore the original user
Auth.auth().updateCurrentUser(originalUser, completion: nil)
Here is a simple solution using web SDKs.
Create a cloud function (https://firebase.google.com/docs/functions)
import admin from 'firebase-admin';
import * as functions from 'firebase-functions';
const createUser = functions.https.onCall((data) => {
return admin.auth().createUser(data)
.catch((error) => {
throw new functions.https.HttpsError('internal', error.message)
});
});
export default createUser;
Call this function from your app
import firebase from 'firebase/app';
const createUser = firebase.functions().httpsCallable('createUser');
createUser({ email, password })
.then(console.log)
.catch(console.error);
Optionally, you can set user document information using the returned uid.
createUser({ email, password })
.then(({ data: user }) => {
return database
.collection('users')
.doc(user.uid)
.set({
firstname,
lastname,
created: new Date(),
});
})
.then(console.log)
.catch(console.error);
I got André's very clever workaround working in Objective-C using the Firebase iOS SDK:
NSString *plistPath = [[NSBundle mainBundle] pathForResource:#"GoogleService-Info" ofType:#"plist"];
FIROptions *secondaryAppOptions = [[FIROptions alloc] initWithContentsOfFile:plistPath];
[FIRApp configureWithName:#"Secondary" options:secondaryAppOptions];
FIRApp *secondaryApp = [FIRApp appNamed:#"Secondary"];
FIRAuth *secondaryAppAuth = [FIRAuth authWithApp:secondaryApp];
[secondaryAppAuth createUserWithEmail:user.email
password:user.password
completion:^(FIRUser * _Nullable user, NSError * _Nullable error) {
[secondaryAppAuth signOut:nil];
}];
Update for Swift 4
I have tried a few different options to create multiple users from a single account, but this is by far the best and easiest solution.
Original answer by Nico
First Configure firebase in your AppDelegate.swift file
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
FirebaseApp.configure()
FirebaseApp.configure(name: "CreatingUsersApp", options: FirebaseApp.app()!.options)
return true
}
Add the following code to action where you are creating the accounts.
if let secondaryApp = FirebaseApp.app(name: "CreatingUsersApp") {
let secondaryAppAuth = Auth.auth(app: secondaryApp)
// Create user in secondary app.
secondaryAppAuth.createUser(withEmail: email, password: password) { (user, error) in
if error != nil {
print(error!)
} else {
//Print created users email.
print(user!.email!)
//Print current logged in users email.
print(Auth.auth().currentUser?.email ?? "default")
try! secondaryAppAuth.signOut()
}
}
}
}
You can use firebase function for add users.
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
const cors = require('cors')({
origin: true,
});
exports.AddUser = functions.https.onRequest(( req, res ) => {
// Grab the text parameter.
cors( req, res, () => {
let email = req.body.email;
let passwd = req.body.passwd;
let role = req.body.role;
const token = req.get('Authorization').split('Bearer ')[1];
admin.auth().verifyIdToken(token)
.then(
(decoded) => {
// return res.status(200).send( decoded )
return creatUser(decoded);
})
.catch((err) => {
return res.status(401).send(err)
});
function creatUser(user){
admin.auth().createUser({
email: email,
emailVerified: false,
password: passwd,
disabled: false
})
.then((result) => {
console.log('result',result);
return res.status(200).send(result);
}).catch((error) => {
console.log(error.message);
return res.status(400).send(error.message);
})
}
});
});
CreateUser(){
//console.log('Create User')
this.submitted = true;
if (this.myGroup.invalid) {
return;
}
let Email = this.myGroup.value.Email;
let Passwd = this.myGroup.value.Passwd;
let Role = 'myrole';
let TechNum = this.myGroup.value.TechNum;
let user = JSON.parse(localStorage.getItem('user'));
let role = user.role;
let AdminUid = user.uid;
let authToken = user.stsTokenManager.accessToken;
let httpHeaders = new HttpHeaders().set('Authorization', 'Bearer ' + authToken);
let options = { headers: httpHeaders };
let params = { email:Email,passwd:Passwd,role:Role };
this.httpClient.post('https://us-central1-myproject.cloudfunctions.net/AddUser', params, options)
.subscribe( val => {
//console.log('Response from cloud function', val );
let createdUser:any = val;
//console.log(createdUser.uid);
const userRef: AngularFirestoreDocument<any> = this.afs.doc(`users/${createdUser.uid}`);
const userUpdate = {
uid: createdUser.uid,
email: createdUser.email,
displayName: null,
photoURL: null,
emailVerified: createdUser.emailVerified,
role: Role,
TechNum:TechNum,
AccountAccess:this.AccountAccess,
UserStatus:'open',
OwnerUid:AdminUid,
OwnerUidRole:role,
RootAccountAccess:this.RootAccountAccess
}
userRef.set(userUpdate, {
merge: false
});
this.toastr.success('Success, user add','Success');
this.myGroup.reset();
this.submitted = false;
},
err => {
console.log('HTTP Error', err.error)
this.toastr.error(err.error,'Error')
},
() => console.log('HTTP request completed.')
);
}
On the web, this is due to unexpected behavior when you call createUserWithEmailAndPassword out of the registration context; e.g. inviting a new user to your app by creating a new user account.
Seems like, createUserWithEmailAndPassword method triggers a new refresh token and user cookies are updated too. (This side-effect is not documented)
Here is a workaround for Web SDK:
After creating the new user;
firebase.auth().updateCurrentUser (loggedInUser.current)
provided that you initiate loggedInUser with the original user beforehand.
Hey i had similar problem ,trying to create users through admin , as it is not possible to signUp user without signIn ,I created a work around ,adding it below with steps
Instead of signup create a node in firebase realtime db with email as key (firebase do not allow email as key so I have created a function to generate key from email and vice versa, I will attach the functions below)
Save a initial password field while saving user (can even hash it with bcrypt or something, if you prefer though it will be used one time only)
Now Once user try to login check if any node with that email (generate key from email) exist in the db and if so then match the password provided.
If the password matched delete the node and do authSignUpWithEmailandPassword with provided credentials.
User is registered successfully
//Sign In
firebaseDB.child("users").once("value", (snapshot) => {
const users = snapshot.val();
const userKey = emailToKey(data.email);
if (Object.keys(users).find((key) => key === userKey)) {
setError("user already exist");
setTimeout(() => {
setError(false);
}, 2000);
setLoading(false);
} else {
firebaseDB
.child(`users`)
.child(userKey)
.set({ email: data.email, initPassword: data.password })
.then(() => setLoading(false))
.catch(() => {
setLoading(false);
setError("Error in creating user please try again");
setTimeout(() => {
setError(false);
}, 2000);
});
}
});
//Sign Up
signUp = (data, setLoading, setError) => {
auth
.createUserWithEmailAndPassword(data.email, data.password)
.then((res) => {
const userDetails = {
email: res.user.email,
id: res.user.uid,
};
const key = emailToKey(data.email);
app
.database()
.ref(`users/${key}`)
.remove()
.then(() => {
firebaseDB.child("users").child(res.user.uid).set(userDetails);
setLoading(false);
})
.catch(() => {
setLoading(false);
setError("error while registering try again");
setTimeout(() => setError(false), 4000);
});
})
.catch((err) => {
setLoading(false);
setError(err.message);
setTimeout(() => setError(false), 4000);
});
};
//Function to create a valid firebase key from email and vice versa
const emailToKey = (email) => {
//firebase do not allow ".", "#", "$", "[", or "]"
let key = email;
key = key.replace(".", ",0,");
key = key.replace("#", ",1,");
key = key.replace("$", ",2,");
key = key.replace("[", ",3,");
key = key.replace("]", ",4,");
return key;
};
const keyToEmail = (key) => {
let email = key;
email = email.replace(",0,", ".");
email = email.replace(",1,", "#");
email = email.replace(",2,", "$");
email = email.replace(",3,", "[");
email = email.replace(",4,", "]");
return email;
};
If you want to do it in your front end create a second auth reference use it to create other users and sign out and delete that reference. If you do it this way you won't be signed out when creating a new user and you won't get the error that the default firebase app already exists.
const createOtherUser =()=>{
var config = {
//your firebase config
};
let secondaryApp = firebase.initializeApp(config, "secondary");
secondaryApp.auth().createUserWithEmailAndPassword(email, password).then((userCredential) => {
console.log(userCredential.user.uid);
}).then(secondaryApp.auth().signOut()
)
.then(secondaryApp.delete()
)
}
Update 19.05.2022 - using #angular/fire (latest available = v.7.3.0)
If you are not using firebase directly in your app, but use e.g. #angular/fire for auth purposes only, you can use the same approach as suggested earlier as follows with the #angular/fire library:
import { Auth, getAuth, createUserWithEmailAndPassword } from '#angular/fire/auth';
import { deleteApp, initializeApp } from '#angular/fire/app';
import { firebaseConfiguration } from '../config/app.config'; // <-- Your project's configuration here.
const tempApp = initializeApp(firebaseConfiguration, "tempApp");
const tempAppAuth = getAuth(tempApp);
await createUserWithEmailAndPassword(tempAppAuth, email, password)
.then(async (newUser) => {
resolve( () ==> {
// Do something, e.g. add user info to database
});
})
.catch(error => reject(error))
.finally( () => {
tempAppAuth.signOut()
.then( () => deleteApp(tempApp));
});
The Swift version:
FIRApp.configure()
// Creating a second app to create user without logging in
FIRApp.configure(withName: "CreatingUsersApp", options: FIRApp.defaultApp()!.options)
if let secondaryApp = FIRApp(named: "CreatingUsersApp") {
let secondaryAppAuth = FIRAuth(app: secondaryApp)
secondaryAppAuth?.createUser(...)
}
Here is a Swift 3 adaptaion of Jcabrera's answer :
let bundle = Bundle.main
let path = bundle.path(forResource: "GoogleService-Info", ofType: "plist")!
let options = FIROptions.init(contentsOfFile: path)
FIRApp.configure(withName: "Secondary", options: options!)
let secondary_app = FIRApp.init(named: "Secondary")
let second_auth = FIRAuth(app : secondary_app!)
second_auth?.createUser(withEmail: self.username.text!, password: self.password.text!)
{
(user,error) in
print(user!.email!)
print(FIRAuth.auth()?.currentUser?.email ?? "default")
}
If you are using Polymer and Firebase (polymerfire) see this answer: https://stackoverflow.com/a/46698801/1821603
Essentially you create a secondary <firebase-app> to handle the new user registration without affecting the current user.
Android solution (Kotlin):
1.You need FirebaseOptions BUILDER(!) for setting api key, db url, etc., and don't forget to call build() at the end
2.Make a secondary auth variable by calling FirebaseApp.initializeApp()
3.Get instance of FirebaseAuth by passing your newly created secondary auth, and do whatever you want (e.g. createUser)
// 1. you can find these in your project settings under general tab
val firebaseOptionsBuilder = FirebaseOptions.Builder()
firebaseOptionsBuilder.setApiKey("YOUR_API_KEY")
firebaseOptionsBuilder.setDatabaseUrl("YOUR_DATABASE_URL")
firebaseOptionsBuilder.setProjectId("YOUR_PROJECT_ID")
firebaseOptionsBuilder.setApplicationId("YOUR_APPLICATION_ID") //not sure if this one is needed
val firebaseOptions = firebaseOptionsBuilder.build()
// indeterminate progress dialog *ANKO*
val progressDialog = indeterminateProgressDialog(resources.getString(R.string.progressDialog_message_registering))
progressDialog.show()
// 2. second auth created by passing the context, firebase options and a string for secondary db name
val newAuth = FirebaseApp.initializeApp(this#ListActivity, firebaseOptions, Constants.secondary_db_auth)
// 3. calling the create method on our newly created auth, passed in getInstance
FirebaseAuth.getInstance(newAuth).createUserWithEmailAndPassword(email!!, password!!)
.addOnCompleteListener { it ->
if (it.isSuccessful) {
// 'it' is a Task<AuthResult>, so we can get our newly created user from result
val newUser = it.result.user
// store wanted values on your user model, e.g. email, name, phonenumber, etc.
val user = User()
user.email = email
user.name = name
user.created = Date().time
user.active = true
user.phone = phone
// set user model on /db_root/users/uid_of_created_user/, or wherever you want depending on your structure
FirebaseDatabase.getInstance().reference.child(Constants.db_users).child(newUser.uid).setValue(user)
// send newly created user email verification link
newUser.sendEmailVerification()
progressDialog.dismiss()
// sign him out
FirebaseAuth.getInstance(newAuth).signOut()
// DELETE SECONDARY AUTH! thanks, Jimmy :D
newAuth.delete()
} else {
progressDialog.dismiss()
try {
throw it.exception!!
// catch exception for already existing user (e-mail)
} catch (e: FirebaseAuthUserCollisionException) {
alert(resources.getString(R.string.exception_FirebaseAuthUserCollision), resources.getString(R.string.alertDialog_title_error)) {
okButton {
isCancelable = false
}
}.show()
}
}
}
For Android, i suggest a simpler way to do it, without having to provide api key, application id...etc by hand by just using the FirebaseOptions of the default instance.
val firebaseDefaultApp = Firebase.auth.app
val signUpAppName = firebaseDefaultApp.name + "_signUp"
val signUpApp = try {
FirebaseApp.initializeApp(
context,
firebaseDefaultApp.options,
signUpAppName
)
} catch (e: IllegalStateException) {
// IllegalStateException is throw if an app with the same name has already been initialized.
FirebaseApp.getInstance(signUpAppName)
}
// Here is the instance you can use to sign up without triggering auth state on the default Firebase.auth
val signUpFirebaseAuth = Firebase.auth(signUpApp)
How to use ?
signUpFirebaseAuth
.createUserWithEmailAndPassword(email, password)
.addOnSuccessListener {
// Optional, you can send verification email here if you need
// As soon as the sign up with sign in is over, we can sign out the current user
firebaseAuthSignUp.signOut()
}
.addOnFailureListener {
// Log
}
My solution to this question is to store the User Name/Email and password in a static class and then add a new user log out the new user and immediately log in as the admin user(id pass you saved). Works like a charm for me :D
This is a version for Kotlin:
fun createUser(mail: String, password: String) {
val opts = FirebaseOptions.fromResource(requireContext())
if (opts == null) return
val app = Firebase.initialize(requireContext(), opts, "Secondary")
FirebaseAuth.getInstance(app)
.createUserWithEmailAndPassword(mail, password)
.addOnSuccessListener {
app.delete()
doWhateverWithAccount(it)
}.addOnFailureListener {
app.delete()
showException(it)
}
}
It uses the configuration from your default Firebase application instance, just under a different name.
It also deletes the newly created instance afterwards, so you can call this multiple times without any exception about already existing Secondary application.

Resources