Creating a new user via Firebase Cloud Functions - account created but can not sign in - firebase

I am new here.
I am currently developing an app using Vue.js with firebase auth, firebase realtime database and firebase cloud functions as a backend. One part of the functionalities that the app has to include an admin account that has a possibility to create an accounts for other people. After creation new user receives an email with a login and a password to log in.
Because the signing up method (https://firebase.google.com/docs/auth/web/password-auth) automatically relog user to a newly created account which is obviously not wanted behavior I found a way to create a user via cloud functions. The code successfully creates an account in the firebase authentication panel with the exception that I cannot log in to the newly created accounts. I get a message that: "The password is invalid or the user does not have a password".
Additionally I am not sure if it means anything in this case but the accounts created with the cloud functions method does not have a mail icon in firebase authentication panel (Image).
Cloud Function Code:
exports.createUser = functions.https.onCall((data, context) => {
console.log(data)
return admin.auth().createUser({
email: data.email,
emailVerified: true,
password: data.password,
displayName: data.email,
disabled: false,
})
.then(user => {
return {
response: user
}
})
.catch(error => {
throw new functions.https.HttpsError('failed to create a user')
});
});
Sign In Code:
signIn: async function(){
if(this.email && this.password){
let getUsers = firebase.functions().httpsCallable('getUsers')
this.feedback = null
this.spin = true
let destination = null
let logedUser = null
let type = null
this.feedback = 'Logging in...'
await firebase.auth().signInWithEmailAndPassword(this.email, this.password)
.then(response => {
this.feedback = 'Authorization finished...'
logedUser = response.user
})
.catch( error => {
this.feedback = error.message
this.spin = false
})
//... more code here but I am certain it has nothing to do with the problem.

Due to the asynchronous character of the HTTPS callable function, with your current code you are trying to sign-in before the user is completely created through the Cloud Function.
In addition, you are actually not calling the Cloud Function with the mail and password parameters.
You should do as follows, based on the documentation.
....
let getUsers = firebase.functions().httpsCallable('getUsers')
await getUsers({email: this.email, password: this.password})
.then(result => {
return firebase.auth().signInWithEmailAndPassword(this.email, this.password)
})
.then(userCredential => {
this.feedback = 'Authorization finished...'
logedUser = userCredential.user
return true
})
.catch( error => {
this.feedback = error.message
this.spin = false
})
....

Related

Undefined Firebase Custom Claims

I want to add a custom claim to the idtoken on registration here is my cloud function for that:
exports.processSignUp = functions.auth.user().onCreate((newUser) => {
let customClaims = {};
if (newUser.role === "reader") {
customClaims.reader = true
} else if (newUser.role === "writer") {
customClaims.writer = true
}
return admin
.auth()
.setCustomUserClaims(newUser.uid, customClaims)
.then(() => {
return {
message: `success`,
};
});
});
(The newUser object contains a role key that can hold either a string value of writer or reader)
whenever I create a new user the cloud function runs with status ok but later on when I receive the decoded id token there aren't any claims in it so I assume it was null to begin with. I have no idea where my mistake is and couldn't find any documentation that could explain it. Would very much appreciate any help :))
The newUser parameter in the function is of type auth.UserRecord which does not have any custom properties. It only has these properties.
It seems to me that you want to add role - either writer or reader - to a newly created user. Using a custom callable cloud function rather than authentication trigger would be better to avoid race conditions involved in other workarounds.
exports.createNewUser = functions.https.onCall((data, context) => {
const userEmail = data.email;
const userPassword = data.password
const role = data.role
if (!["reader", "writer"].includes(role)) {
return {error: "Invalid Role"}
}
return admin.auth().createUser({ email: userEmail, password: userPassword }).then((newUser) => {
return admin.auth().setCustomUserClaims(newUser.uid, {role: role}).then(() => {
return {data: "New user created successfully"}
})
})
});
This will create a new user with role you need. To call this function from your app, follow this code on the client:
const createNewUser = firebase.functions().httpsCallable('createNewUser');
createNewUser({ email: "userEmail", password: "userPassword", role: "userRole" })
.then((result) => {
// Read result of the Cloud Function.
const response = result.data.data;
});
Please let me know if you need more clarification.

Integrate custom Oauth provider with firebase.auth().signInWithRedirect?

I setup a Twitch OAuth integration using the Instagram example, now I can login into my app by opening the popup.html page that the example gave me.
Here's my adapted code:
'use strict';
const functions = require('firebase-functions');
const admin = require('firebase-admin');
const cookieParser = require('cookie-parser');
const crypto = require('crypto');
const { AuthorizationCode } = require('simple-oauth2');
const fetch = require('node-fetch');
// Firebase Setup
const admin = require('firebase-admin');
// #ts-ignore
const serviceAccount = require('./service-account.json');
admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
databaseURL: `https://${process.env.GCLOUD_PROJECT}.firebaseio.com`,
});
const OAUTH_REDIRECT_URI = `https://${process.env.GCLOUD_PROJECT}.firebaseapp.com/popup.html`;;
const OAUTH_SCOPES = 'user:read:email';
/**
* Creates a configured simple-oauth2 client for Twitch.
*/
function twitchOAuth2Client() {
// Twitch OAuth 2 setup
// TODO: Configure the `twitch.client_id` and `twitch.client_secret` Google Cloud environment variables.
const credentials = {
client: {
id: functions.config().twitch.client_id,
secret: functions.config().twitch.client_secret,
},
auth: {
tokenHost: 'https://id.twitch.tv',
tokenPath: '/oauth2/token',
authorizePath: '/oauth2/authorize',
},
options: {
bodyFormat: 'json',
authorizationMethod: 'body',
},
};
return new AuthorizationCode(credentials);
}
/**
* Redirects the User to the Twitch authentication consent screen. Also the 'state' cookie is set for later state
* verification.
*/
exports.redirect = functions.https.onRequest((req, res) => {
const authorizationCode = twitchOAuth2Client();
cookieParser()(req, res, () => {
const state = req.cookies.__session || crypto.randomBytes(20).toString('hex');
console.log('Setting verification state:', state);
res.cookie('__session', state.toString(), { maxAge: 3600000, httpOnly: true });
const redirectUri = authorizationCode.authorizeURL({
redirect_uri: OAUTH_REDIRECT_URI,
scope: OAUTH_SCOPES,
state: state,
});
console.log('Redirecting to:', redirectUri);
res.redirect(redirectUri);
});
});
/**
* Exchanges a given Twitch auth code passed in the 'code' URL query parameter for a Firebase auth token.
* The request also needs to specify a 'state' query parameter which will be checked against the 'state' cookie.
* The Firebase custom auth token, display name, photo URL and Twitch acces token are sent back in a JSONP callback
* function with function name defined by the 'callback' query parameter.
*/
exports.token = functions.https.onRequest((req, res) => {
const authorizationCode = twitchOAuth2Client();
try {
cookieParser()(req, res, async () => {
try {
console.log('Received verification state:', req.cookies.__session);
console.log('Received state:', req.query.state);
if (!req.cookies.__session) {
throw new Error(
'State cookie not set or expired. Maybe you took too long to authorize. Please try again.'
);
} else if (req.cookies.__session !== req.query.state) {
throw new Error('State validation failed');
}
} catch (error) {
return res.jsonp({ error: error.toString() });
}
let accessToken;
try {
console.log('Received auth code:', req.query.code);
const options = {
client_id: functions.config().twitch.client_id,
client_secret: functions.config().twitch.client_secret,
code: req.query.code,
grant_type: 'authorization_code',
redirect_uri: OAUTH_REDIRECT_URI,
};
console.log('Asking token with options', JSON.stringify(options));
accessToken = await authorizationCode.getToken(options);
console.log('Auth code exchange result received');
const twitchUser = await getTwitchUser(accessToken.toJSON().access_token);
// Create a Firebase account and get the Custom Auth Token.
const firebaseToken = await createFirebaseAccount(twitchUser);
// Serve an HTML page that signs the user in and updates the user profile.
return res.jsonp({ token: firebaseToken });
} catch (error) {
return res.jsonp({ error: error.toString() });
}
});
} catch (error) {
return res.jsonp({ error: error.toString() });
}
});
/**
* Creates a Firebase account with the given user profile and returns a custom auth token allowing
* signing-in this account.
*
* #returns {Promise<string>} The Firebase custom auth token in a promise.
*/
async function createFirebaseAccount(twitchUser) {
// The UID we'll assign to the user.
const uid = `twitch:${twitchUser.id}`;
// Save the access token to the Firebase Database.
const db = admin.firestore();
const databaseTask = db.collection('users').doc(uid).set(twitchUser);
// Create or update the user account.
const userCreationTask = admin
.auth()
.updateUser(uid, {
displayName: twitchUser['display_name'],
photoURL: twitchUser['profile_image_url'],
email: twitchUser['email'],
})
.catch((error) => {
// If user does not exists we create it.
if (error.code === 'auth/user-not-found') {
return admin.auth().createUser({
uid: uid,
displayName: twitchUser['display_name'],
photoURL: twitchUser['profile_image_url'],
email: twitchUser['email'],
});
}
throw error;
});
// Wait for all async task to complete then generate and return a custom auth token.
await Promise.all([userCreationTask, databaseTask]);
// Create a Firebase custom auth token.
const token = await admin.auth().createCustomToken(uid);
console.log('Created Custom token for UID "', uid, '" Token:', token);
return token;
}
async function getTwitchUser(accessToken) {
console.log('Fetching Twitch user with access_token', accessToken);
try {
const response = await fetch('https://api.twitch.tv/helix/users', {
method: 'GET',
headers: {
'Client-Id': functions.config().twitch.client_id,
Authorization: 'Bearer ' + accessToken,
},
});
const data = await response.json();
return { ...data.data[0], access_token: accessToken };
} catch (error) {
console.error(error);
}
}
I'd like, though, to login into Twitch using the firebase.auth().signInWithRedirect() method that I already use for Facebook and Google, unfortunately I can't find any documentation about this, and the Facebook provider source code refers to some externs.* resources so I'm not sure how to adapt it for my own needs.
Right now I have two endpoints/cloud functions: _twitchRedirect and _twitchToken, what should I do to integrate them with signInWithRedirect?
I was similarly curious, so spent a little time playing around with things today.
In short, when using Firebase Auth, I believe the providerId will need to be one of the existing supported providers.
If you upgrade to using the Google Cloud Identity Platform though, I believe you will be able to configure custom providers, and then use this function to authenticate:
https://cloud.google.com/identity-platform
We can see that firebase.auth.OAuthProvider and firebase.auth().signInWithPopup (or firebase.auth().signInWithRedirect) are used with a number of the providers here, eg.
https://cloud.google.com/identity-platform/docs/web/apple
https://cloud.google.com/identity-platform/docs/web/microsoft
In addition to these provider choices that we get with the standard Firebase Auth, Google Cloud Identity Platform allows us to also add SAML and OpenID Connect (OIDC) integrations:
https://cloud.google.com/identity-platform/docs/web/saml
https://cloud.google.com/identity-platform/docs/web/oidc
When adding a new identity provider using either of these, we are able to specify the 'Provider ID' to use (prefixed with either saml. or oidc.). This custom provider ID is then used with firebase.auth.OAuthProvider and firebase.auth().signInWithPopup (or firebase.auth().signInWithRedirect) as described above.
For example, if I created a new identity provider with an ID of oidc.foo, my integration code would end up looking like:
const provider = new firebase.auth.OAuthProvider('oidc.foo');
firebase.auth().signInWithPopup(provider)
.then((result) => {
// result.credential is a firebase.auth.OAuthCredential object.
// result.credential.providerId is equal to 'oidc.foo'.
// result.credential.idToken is the OIDC provider's ID token.
})
.catch((error) => {
// Handle error.
});
Based on my understanding of this, I believe we will only currently be able to add custom providers this way if they conform to the OpenID Connect (OIDC) standard (including the OIDC Discovery part, which uses a /.well-known/openid-configuration URL):
Note: If your OIDC provider doesn't comply with the OIDC specification for discovery, it won't work with Identity Platform.
So to my knowledge, the best way to implement 'normal' OAuth2 providers currently is the custom backend function flow you used above (based on the Firebase Auth examples).
As part of figuring this out, I decided to see what would happen if I used a provider ID that didn't match anything configured in my account (this is a fairly verbose step by step, and the main answer is already included above, but this may help provide some more context/help someone out, so including it here)
var provider = new firebase.auth.OAuthProvider("foo.example.com");
firebase
.auth()
.signInWithRedirect(provider)
.then((result) => console.log("OAuthProvider:", result))
.catch((error) => console.log("OAuthProvider::error:", error));
firebase
.auth()
.getRedirectResult()
.then((result) => console.log("RedirectResult:", result))
.catch((error) => console.log("RedirectResult::error:", error));
At first I go this auth/auth-domain-config-required error:
OAuthProvider::error: {
"code": "auth/auth-domain-config-required",
"message": "Be sure to include authDomain when calling firebase.initializeApp(), by following the instructions in the Firebase console."
}
I figured maybe this should be set to the OAuth provider I was wanting to login to, so I set authDomain in my firebase config to foo.myauthprovider.com, but when I called signInWithRedirect, it tried to load the following URL (where the apiKey is the API key of my firebase project), which failed to load:
https://foo.myauthprovider.com/__/auth/handler?apiKey=REDACTED&appName=%5BDEFAULT%5D&authType=signInViaRedirect&providerId=foo.example.com&redirectUrl=http%3A%2F%2Flocalhost%3A3000%2F&v=7.14.5
This /__/auth/handler URL is part of Firebase Auth's reserved URLs, which you can read more about at:
https://firebase.google.com/docs/hosting/reserved-urls#auth_helpers
And is explained a little better in this StackOverflow answer, but is basically what Firebase Auth uses to handle OAuth callbacks to avoid needing to expose sensitive credentials on the frontend, and so users don't need to implement their own handlers all the time):
Why does Firebase auth uses a "middleware" redirect before returning to my app?
Changing authDomain to the actual custom domain of my firebase project fixed that issue, and then resulted in the following auth/operation-not-allowed error when I tried to redirect:
RedirectResult::error: u {code: "auth/operation-not-allowed", message: "The identity provider configuration is not found.", a: null}

Firebase Google login not staying persistence

I am developing an application, with an feature of Google Login through Firebase. I am trying to login via Google with the help of an library, known as react-native-google-signin. It is well known library in the field of ReactNative for Google Login.
My problem is not with this library, but the problem is that while I am using react-native-google-signin library with firebase to login via google. Firebase User is not staying persistence, I mean to say that when I am opening app after close FirebaseUser is getting null. Below the code I am using to login via firebase,
GoogleSignin.signIn().then(data => {
const credentials = firebase.auth.GoogleAuthProvider.credential(data.idToken, data.accessToken);
firebase.auth().setPersistence(firebase.auth.Auth.Persistence.LOCAL)
.then() => {
return firebase.auth().signInWithCredential(credentials);
}).catch(error => {
console.log('Error', error);
})
}).then(user => {
console.log('user', firebase.auth().currentUser);
}).catch(error => {
console.log('Error', error);
})
I also checked Firebase Docs, tried by using setPersistence() method but still I am getting null user after open app again.
You can try this
when you first time open your app you get user null, but after login one time then reopen your app and you will get previously logged in user in your console
async _setupGoogleSignin() {
try {
await GoogleSignin.hasPlayServices({ autoResolve: true });
await GoogleSignin.configure({
webClientId: 'YOUR WEBCLIENTID',
offlineAccess: false
});
const user = await GoogleSignin.currentUserAsync();
console.log("user",user); // HERE YOU GET LOGGED IN USER IN YOUR CONSOLE FIRST TIME IT WILL BE NULL BUT AFTER YOU GET PREVIOUSLY LOGGED IN USER
this.setState({user});
}
catch(err) {
console.log("Play services error", err.code, err.message);
}}
then
_signIn() {
GoogleSignin.signIn()
.then((user) => {
console.log(user);
this.setState({user: user});
const credential = firebase.auth.GoogleAuthProvider.credential(user.idToken, user.accessToken);
// console.log(credential);
return firebase.auth().signInAndRetrieveDataWithCredential(credential);
})
.catch((err) => {
console.log('WRONG SIGNIN', err);
})
.done();}
it is worked for me...
hope it will help you...

Is it possible to use Cypress e2e testing with a firebase auth project?

I am exploring Cypress for e2e testing, looks like great software.
The problem is Authentication, the Cypress documentation explains why using the UI is very bad here.
So I tried looking at the network tap of my application, to see if I could create a POST request to the firebase API, and authenticate without using the GUI. But I can see that there at least 2 request fired, and token saved to application storage.
So what approach should I use?
Authenticate with the UI of my application, and instruct Cypress not to touch the local storage
Keep experimenting with a way of sending the correct POST requests, and save the values to local storage.
Make Cypress run custom JS code, and then use the Firebase SDK to login.
I am really looking for some advice here :)
When doing this myself I made custom commands (like cy.login for auth then cy.callRtdb and cy.callFirestore for verifying data). After getting tired of repeating the logic it took to build them, I wrapped it up into a library called cypress-firebase. It includes custom commands and a cli to generate a custom auth token.
Setup mostly just consists of adding the custom commands in cypress/support/commands.js:
import firebase from 'firebase/app';
import 'firebase/auth';
import 'firebase/database';
import 'firebase/firestore';
import { attachCustomCommands } from 'cypress-firebase';
const fbConfig = {
// Your config from Firebase Console
};
window.fbInstance = firebase.initializeApp(fbConfig);
attachCustomCommands({ Cypress, cy, firebase })
And adding the plugin to cypress/plugins/index.js:
const cypressFirebasePlugin = require('cypress-firebase').plugin
module.exports = (on, config) => {
// `on` is used to hook into various events Cypress emits
// `config` is the resolved Cypress config
// Return extended config (with settings from .firebaserc)
return cypressFirebasePlugin(config)
}
But there full details on setup are available in the setup docs.
Disclosure, I am the author of cypress-firebase, which is the whole answer.
I took the approach of using automated UI to obtain the contents of localStorage used by Firebase JS SDK. I also wanted to do this only once per whole Cypress run so I did it before the Cypress start.
Obtain Firebase SDK localStorage entry via pupeteer
Store the contents in the tmp file (problems passing it via env var to Cypress)
Pass the file location to Cypress via env var and let it read the contents and set the localStorage to setup the session
Helper script which obtains contents of localStorage:
const puppeteer = require('puppeteer')
const invokeLogin = async page => {
await page.goto('http://localhost:3000/login')
await page.waitForSelector('.btn-googleplus')
await page.evaluate(() =>
document.querySelector('.btn-googleplus').click())
}
const doLogin = async (page, {username, password}) => {
// Username step
await page.waitForSelector('#identifierId')
await page.evaluate((username) => {
document.querySelector('#identifierId').value = username
document.querySelector('#identifierNext').click()
}, username)
// Password step
await page.waitForSelector('#passwordNext')
await page.evaluate(password =>
setTimeout(() => {
document.querySelector('input[type=password]').value = password
document.querySelector('#passwordNext').click()
}, 3000) // Wait 3 second to next phase to init (couldn't find better way)
, password)
}
const extractStorageEntry = async page =>
page.evaluate(() => {
for (let key in localStorage) {
if (key.startsWith('firebase'))
return {key, value: localStorage[key]}
}
})
const waitForApp = async page => {
await page.waitForSelector('#app')
}
const main = async (credentials, cfg) => {
const browser = await puppeteer.launch(cfg)
const page = await browser.newPage()
await invokeLogin(page)
await doLogin(page, credentials)
await waitForApp(page)
const entry = await extractStorageEntry(page)
console.log(JSON.stringify(entry))
await browser.close()
}
const username = process.argv[2]
const password = process.argv[3]
main({username, password}, {
headless: true // Set to false for debugging
})
Since there were problem with sending JSON as environment variables to Cypress I use tmp file to pass the data between the script and the Cypress process.
node test/getFbAuthEntry ${USER} ${PASSWORD} > test/tmp/fbAuth.json
cypress open --env FB_AUTH_FILE=test/tmp/fbAuth.json
In Cypress I read it from the file system and set it to the localStorage
const setFbAuth = () =>
cy.readFile(Cypress.env('FB_AUTH_FILE'))
.then(fbAuth => {
const {key, value} = fbAuth
localStorage[key] = value
})
describe('an app something', () => {
it('does stuff', () => {
setFbAuth()
cy.viewport(1300, 800)
...
This is certainly a hack but to get around the login part for the app I am working on I use the beforeEach hook to login to the application.
beforeEach(() => {
cy.resetTestDatabase().then(() => {
cy.setupTestDatabase();
});
});
Which is derived from my helper functions.
Cypress.Commands.add('login', () => {
return firebase
.auth()
.signInWithEmailAndPassword(Cypress.env('USER_EMAIL'), Cypress.env('USER_PASSWORD'));
});
Cypress.Commands.add('resetTestDatabase', () => {
return cy.login().then(() => {
firebase
.database()
.ref(DEFAULT_CATEGORIES_PATH)
.once('value')
.then(snapshot => {
const defaultCategories = snapshot.val();
const updates = {};
updates[TEST_CATEGORIES_PATH] = defaultCategories;
updates[TEST_EVENTS_PATH] = null;
updates[TEST_STATE_PATH] = null;
updates[TEST_EXPORT_PATH] = null;
return firebase
.database()
.ref()
.update(updates);
});
});
});
What I would like to know is how the information coming back from firebase ultimately gets saved to localStorage. I don't really have an answer to this but it works. Also, the app uses .signInWithPopup(new firebase.auth.GoogleAuthProvider()) whereas above it signs in with email and password. So I am kind of shortcutting the signin process only because cypress has the CORS limitation.
This is becoming way easier with the upcoming Auth emulator. This has become easier with the Firebase Auth Emulator (firebase-tools >= 8.1.4).
cypress/support/signAs.js:
Cypress.Commands.add('signAs', (uid, opt) => {
cy.visit('/')
cy.window().its('firebase').then( fb => {
cy.wrap( (async _ => {
// Create a user based on the provided token (only '.uid' is used by Firebase)
await fb.auth().signInWithCustomToken( JSON.stringify({ uid }) );
// Set '.displayName', '.photoURL'; for email and password, other functions exist (not implemented)
await fb.auth().currentUser.updateProfile(opt);
})() )
})
})
Use it as:
cy.signAs('joe', { displayName: 'Joe D.', photoURL: 'http://some' });
If you need to set .email or .password, there are similar functions for those, but this was sufficient for my tests. I can now impersonate any user ad-hoc, as part of the test. The approach does not need users to be created in the emulator; you can just claim to be one, with the particular uid. Works well for me.
Note:
Firebase authentication is in IndexedDB (as mentioned in other answers) and Cypress does not clear it, between the tests. There is discussion about this in cypress #1208.
At the time writing, I've examined these approaches
stubbing firebase network requests - really difficult. A bunch of firebase requests is sent continuously. There are so many request params & large payload and they're unreadable.
localStorage injection - as same as request stubbing. It requires an internally thorough understanding of both firebase SDK and data structure.
cypress-firebase plugin - it's not matured enough and lack of documentation. I skipped this option because it needs a service account (admin key). The project I'm working on is opensource and there are many contributors. It's hard to share the key without including it in the source control.
Eventually, I implemented it on my own which is quite simple. Most importantly, it doesn't require any confidential firebase credentials. Basically, it's done by
initialize another firebase instance within Cypress
use that firebase instance to build a Cypress custom command to login
const fbConfig = {
apiKey: `your api key`, // AIzaSyDAxS_7M780mI3_tlwnAvpbaqRsQPlmp64
authDomain: `your auth domain`, // onearmy-test-ci.firebaseapp.com
projectId: `your project id`, // onearmy-test-ci
}
firebase.initializeApp(fbConfig)
const attachCustomCommands = (
Cypress,
{ auth, firestore }: typeof firebase,
) => {
let currentUser: null | firebase.User = null
auth().onAuthStateChanged(user => {
currentUser = user
})
Cypress.Commands.add('login', (email, password) => {
Cypress.log({
displayName: 'login',
consoleProps: () => {
return { email, password }
},
})
return auth().signInWithEmailAndPassword(email, password)
})
Cypress.Commands.add('logout', () => {
const userInfo = currentUser ? currentUser.email : 'Not login yet - Skipped'
Cypress.log({
displayName: 'logout',
consoleProps: () => {
return { currentUser: userInfo }
},
})
return auth().signOut()
})
}
attachCustomCommands(Cypress, firebase)
Here is the commit that has all integration code https://github.com/ONEARMY/community-platform/commit/b441699c856c6aeedb8b73464c05fce542e9ead1
Ok after much trial and error, I tried solution path 2 and it worked.
So my auth flow looks like this:
Send POST request (using cybress.request) to
https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyPassword,
and parse the response. Create an object: response1 = response.body
Send POST request (using cybress.request) to
https://www.googleapis.com/identitytoolkit/v3/relyingparty/getAccountInfo,
use the idToken from the prev request. Create an object: user = response2.body.users[0];
Combine the response in an object, with the following properties:
const authObject = {
uid: response1.localId,
displayName: response1.displayName,
photoURL: null,
email: response1.email,
phoneNumber: null,
isAnonymous: false,
providerData: [
{
uid: response1.email,
displayName: response1.displayName,
photoURL: null,
email: body.email,
phoneNumber: null,
providerId: 'password'
}
],
'apiKey': apiKey,
'appName': '[DEFAULT]',
'authDomain': '<name of firebase domain>',
'stsTokenManager': {
'apiKey': apiKey,
'refreshToken': response1.refreshToken,
'accessToken': response1.idToken,
'expirationTime': user.lastLoginAt + Number(response1.expiresIn)
},
'redirectEventId': null,
'lastLoginAt': user.lastLoginAt,
'createdAt': user.createdAt
};
Then in cybress, I simply save this object in local storag, in the before hook: localStorage.setItem(firebase:authUser:${apiKey}:[DEFAULT], authObject);
Maybe not perfect, but it solves the problem. Let me know if you interested in the code, and if you have any knowledge about how to build the "authObject", or solve this problem in another way.

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