Firebase function triggering on auth displayName update? [duplicate] - firebase

I need to run a Firebase function whenever my user updates their email address, except auth only has onCreate and onDelete. How can I react to email updates?

It's not possible today to directly react to an email address changing in Firebase Authentication. If you'd like to see that as a feature, please file a feature request.
You can react to it indirectly by having your app listen to authentication events (Android), take the User object delivered to your listener, and write the user's email address to a RealtimeDatabase location (or Firestore document) for that user's UID. Then, you can have a database trigger that tracks the location of your users in the database, and react to the change there.

My Workaround Use functions.https.onCall to create an HTTPS callable function.
Firebase Function
exports.onUpdateUserEmail = functions.https.onCall((data, context) => {
const uid = context.auth.uid;
const email = context.auth.token.email || null;
return admin.
firestore()
.collection("users")
.doc(uid)
.set({email});
});
Deploy
$ firebase deploy --only functions:addMessage
Call your Fonction in your app nodejs example firebase v9
import { initializeApp } from 'firebase/app';
import { getAuth, updateEmail } from "firebase/auth";
import { getFunctions, httpsCallable } from 'firebase/functions';
const app = initializeApp({
projectId: '### CLOUD FUNCTIONS PROJECT ID ###',
apiKey: '### FIREBASE API KEY ###',
authDomain: '### FIREBASE AUTH DOMAIN ###',
});
const functions = getFunctions(app);
const onUpdateEmail = httpsCallable(functions, 'onUpdateUserEmail');
const auth = getAuth();
updateEmail(auth.currentUser, "user#example.com").then(() => {
// Email updated!
onUpdateEmail();
}).catch((error) => {
// An error occurred
// ...
});

Related

How to retrieve auth cookie on serverInit in Pinia store?

I have recently started learning Nuxt 3 by trying to implement basic user auth logic with firebase and now I would need to retrieve the cookie from the req to initialise the user auth state inside Pinia store but it seems like nuxtServerInit is not supported by Pinia. After realising this I proceeded to a different solution where I'm using server middleware to pass the req object to an action called nuxtServerInit() but then I ran into another problem which is that I cannot call the auth().verifyIdToken(token) from there because the firebase is initialised inside a plugin which runs afterwards.
After this I also tried to initialise firebase inside server middleware and pass auth object to nuxtServerInit() action and set it to the state but then I could not call an action from there I guess because Pinia has not initialised yet?
How could I overcome the original problem? What could be a better approach?
Heres the firebase init plugin:
import { initializeApp } from 'firebase/app'
import { getAuth } from "firebase/auth"
import { getFirestore } from 'firebase/firestore'
import { useStore } from '~/store/index.js'
export default defineNuxtPlugin(nuxtApp => {
const firebaseConfig = {
apiKey: "API_KEY",
authDomain: "AUTH_DOMAIN",
projectId: "PROJECT_ID",
storageBucket: "STORAGE_BUCKET",
messagingSenderId: "MESSAGING_SENDER_ID",
appId: "APP_ID"
};
const app = initializeApp(firebaseConfig);
const auth = getAuth(app)
const firestore = getFirestore(app)
nuxtApp.vueApp.provide('auth', auth)
nuxtApp.provide('auth', auth)
nuxtApp.vueApp.provide('firestore', firestore)
nuxtApp.provide('firestore', firestore)
const store = useStore()
console.log('AUTH', auth)
auth.onAuthStateChanged((user) => {
store.setAuth(user)
})
})

How to make Firestore query during SSR

I would like to fetch data from Firestore during server side rendering. I know I could use REST API (and attach the token to the request's headers) but I don't want to write REST requests on server side and then duplicate the same requests on client side using standard Firestore queries. On client I prefer standard queries (no REST) because of the realtime updates. And I would like to reuse the queries from client also on the server (even without the benefit of realtime updates).
I validate the token manually on the server:
import admin from 'firebase-admin';
import { initializeApp, getApp } from 'firebase/app';
import { getFirestore } from 'firebase/firestore';
const firebaseApp = initializeApp(config);
const db = getFirestore(firebaseApp);
const decodedIdToken = await admin.auth().verifySessionCookie(sessionCookie);
// => token verified: decodedIdToken.userId = "xxx"
But when I want to perform a query:
import { collection, getDocs } from 'firebase/firestore';
const querySnapshot = await getDocs(collection(db, 'myCollection'));
I get error:
{
"code": "permission-denied",
"name": "FirebaseError"
}
Firestore rules:
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write: if request.auth != null;
}
}
}
On client side the error could be solved using onAuthStateChanged but I can't use this listener on server.
Is there any way to run Firestore queries with manually verified token?
I've managed to replicate your error. You're getting this error because you're trying to use client SDK instead of firebase-admin.
Here's the sample code for your reference:
import admin from 'firebase-admin';
import { initializeApp } from 'firebase-admin/app';
import { getFirestore } from "firebase-admin/firestore";
const firebaseApp = initializeApp(config);
const db = getFirestore(firebaseApp);
// const decodedIdToken = await admin.auth().verifySessionCookie(sessionCookie);
// => token verified: decodedIdToken.userId = "xxx"
// const querySnapshot = await getDocs(collection(db, 'myCollection'));
db.collection("myCollection").get().then((querySnapshot) => {
querySnapshot.forEach((doc) => {
// doc.data() is never undefined for query doc snapshots
console.log(doc.id, " => ", doc.data());
});
});
I've used version 8 (commonJS) instead of version 9 (modular) as firebase-admin still uses the dot notation syntax.
[sampleQuery] => { test: 'testing' }
Here's the link on how to get all documents in a collection.
Here's another reference on upgrading to Node.js SDK Admin SDK v10 (modular SDK).
Update:
If you wanted to use the Firestore Security Rules, you need to use custom signed tokens, you need to pass it to signInWithCustomToken so that the client auth can sign in.
Below is a sample code for your reference:
import { getAuth, signInWithCustomToken } from "firebase/auth";
const auth = getAuth();
signInWithCustomToken(auth, token)
.then((userCredential) => {
// Signed in
const user = userCredential.user;
// ...
})
.catch((error) => {
const errorCode = error.code;
const errorMessage = error.message;
// ...
});
A new user will be created and linked all their credentials and the new account will be stored as part of your project, and will be used to identify a user across every app in your project.
You can also allow a user to sign out by calling signOut:
import { getAuth, signOut } from "firebase/auth";
const auth = getAuth();
signOut(auth).then(() => {
// Sign-out successful.
}).catch((error) => {
// An error happened.
});
You can check this documentation on authenticating with Firebase for additional information.

Error when attempting to call SignInWithRedirect for Google sign-in. React Native, Expo, Firebase

I have reinstalled firebase, expo, and refactored to be using V9 with no real progress. My signInWithEmailAndPassword works fine but I receive the same error with both popUp and Redirect.
TypeError: (0, \_auth.signInWithRedirect) is not a function. (In '(0, \_auth.signInWithRedirect)(auth, provider)', '(0, \_auth.signInWithRedirect)' is undefined)
I believe that it must not be importing correctly but I do not see how. I have authorized google auth in the firebase console and as stated tested the standard sign in and create user functions and they both have no error. My config and initialization both work for standard auth as well as firestore connections.
Import statements :
import {
getAuth,
onAuthStateChanged,
signInWithEmailAndPassword,
createUserWithEmailAndPassword,
signInWithRedirect,
GoogleAuthProvider,
} from "firebase/auth";
Handle Google sign in function :
const auth = getAuth();
const signInWithGoogle = () => {
const provider = new GoogleAuthProvider();
signInWithRedirect(auth, provider)
.then((*result*) => {
*// This gives you a Google Access Token. You can use it to access the Google API.*
const credential = GoogleAuthProvider.credentialFromResult(*result*);
const token = credential.accessToken;
*// The signed-in user info.*
const user = *result*.user;
*// ...*
})
.catch((*error*) => {
*// Handle Errors here.*
const errorCode = *error*.code;
const errorMessage = *error*.message;
*// The email of the user's account used.*
const email = *error*.email;
*// The AuthCredential type that was used.*
const credential = GoogleAuthProvider.credentialFromError(*error*);
*// ...*
});
};
Any guidance on what I am missing is greatly appreciated.

How to disable account creation in firebase authentication

I've a project in which I used to authenticate the users with firebase-auth.In my project users can not create their accounts on their own.Only admin have the privilege to add the user accounts.
In order to use onAuthStateChanged() function I must use firebase-auth in my page.But the issue is because of using firebase-auth on client side one can esaily create accounts by running createUserWithEmailAndPassword() function on the console without having the admin privilege.
Now how can I restrict the people from using createUserWithEmailAndPassword() function on client side?
The only way you can stop clients apps from creating accounts is to disable all authentication providers for your project in the Firebase console. You could write an auth onCreate Cloud Function that attempts to figure out if a new account was created by client or admin code if you want to try to delete it immediately.
I think you can add a claim once the user is added, via a cloud function, which requires authorization, so that if the user doesn't have that claim he can't use the app or can't login.
In 2022 with Firebase Auth with Identity Platform and blocking functions, we can accomplish that the following way:
Create an HTTP function that receives email, password and displayName, and creates user using firebase-admin:
import { https } from 'firebase-functions';
import { getAuth } from 'firebase-admin/auth';
import cors from 'cors';
const auth = getAuth();
// Register an HTTP function with the Functions Framework
export const signupUser = https.onRequest((req, res) => {
const options = {
origin: 'http://localhost:3000'
};
cors(options)(req, res, () => {
console.log('all good');
auth
.createUser({
email: 'example#email.com',
emailVerified: false,
password: 'secretPassword',
displayName: 'John Doe',
disabled: false,
})
.then((userRecord) => {
// See the UserRecord reference doc for the contents of userRecord.
console.log('Successfully created new user:', userRecord.uid);
})
.catch((error) => {
console.log('Error creating new user:', error);
});
// Send an HTTP response
res.send('OK');
});
});
Modify response and origin in CORS as you need.
Now create a blocking beforeCreate function and check for user's display name, if there is no display name, throw an error:
import { auth } from "firebase-functions";
import { initializeApp, applicationDefault } from 'firebase-admin/app';
import { getAuth } from 'firebase-admin/auth';
import postmark from 'postmark';
const app = initializeApp({
credential: applicationDefault(),
projectId: 'your_project_id',
});
const tnc = getAuth(app);
export const signUp = auth
.user().beforeCreate((user, context) => {
if (!user.displayName) {
throw new auth.HttpsError('permission-denied');
}
});
This will work because there is no way to include "display name" when signing up via client side
So you, in short, point is to create a Cloud Function that will register users and make sure to add the check to beforeCreate for something that you know is only possible to do on server-side via firebase-admin sdk.
EDIT: CORRECTION
Just found out you can now disable client side signup from Firebase Console if you have Auth + Identity Platform

Facebook login with Firebase in React Native error 400

I am having trouble to register a user in Firebase with his Facebook credentials in RN. The Facebook and the Firebase apps are setup correctly, since everything is working as expected in Swift. The OAuth redirect URI is also setup correctly.
However when I try to do the same process from RN it fails. My setup is the following, when the user taps my login button I call FB's LoginManager.logInWithReadPermissions(['public_profile', 'email']) and on its success I call FirebaseManager's (which is my custom class for managing Firebase) signUpWithFacebook(). I get a FB access token correctly and I can see that a credential object is created.
However, Firebase always returns the error:
{"error":{"errors":[{"domain":"global","reason":"invalid","message":"A system error has occurred"}],"code":400,"message":"A system error has occurred"}}
The FirebaseManager looks like this:
import { AsyncStorage, NativeModules } from "react-native";
import * as firebase from 'firebase';
const firebaseConfig = {
apiKey: "key",
authDomain: "domain",
databaseURL: "db_url",
projectId: "project_id",
storageBucket: "storage_bucket"
};
const firebaseApp = firebase.initializeApp(firebaseConfig);
const FBSDK = require('react-native-fbsdk');
const {
AccessToken
} = FBSDK;
export default class FirebaseManager {
constructor() {
// Some logic...
}
signUpWithFacebook() {
AccessToken.getCurrentAccessToken().then((data) => {
let accessToken = data.accessToken
console.log('FB accessToken: ' + accessToken)
const provider = new firebase.auth.FacebookAuthProvider();
const credential = provider.credential(accessToken);
console.log('FB credential: ' + credential)
firebase.auth().signInWithCredential(credential)
.then()
.catch((error) => {
console.log('Failed to sign in Firebase with Facebook. ' + error)
})
})
}
}
I would also like to note that Firebase's anonymous & email/password authentication are working with no problem.
My workaround for the moment is to do the Facebook login from Swift and return the user object in RN with a bridge and RN's NativeModules.
Some more info about my project:
react-native: 0.44.2
firebase: 4.0.0
react-native-fbsdk: 0.6.0
Any help would be appreciated!
Might be a long shoot but this looks VERY similar to a an issue in firebase JS SDK. The solution was to not make an instance of FacebookAuthProvider, by changing these lines:
const provider = new firebase.auth.FacebookAuthProvider();
const credential = provider.credential(accessToken);
to this:
var credential = firebase.auth.FacebookAuthProvider.credential(accessToken);

Resources