Get current users access token from Firebase in React Native - firebase

I am trying to get the Firebase authentication access token within a React Native application so that I can authenticate my API calls to a custom server. The Firebase documentation says I should get this token by using auth().currentUser.getIdToken(); however currentUser returns null.
I've tried to use getIdToken() in multiple areas of the application. I know the access token is generated as I can see it in the logs while using expo (user.stsTokenManager.accessToken).
Why is currentUser returning null and how can I get the accessToken?

You need to wrap user.getIdToken() inside of firebase.auth().onAuthStateChanged for user to be available. You can then use jwtToken in your header to authenticate your API calls. You need to import your Firebase configuration file for this to work.
let jwtToken = firebase.auth().onAuthStateChanged(function(user) {
if (user) {
user.getIdToken().then(function(idToken) { // <------ Check this line
alert(idToken); // It shows the Firebase token now
return idToken;
});
}
});

Just putting await before will work too just like this:
await auth().currentUser.getIdToken();

getIdToken returns a promise
firebase.auth()
.signInWithCredential(credential)
.then(async data => {
const jwtToken = await data.user?.getIdToken();
console.log(jwtToken);
})

Hook example
Unfortunately, its not reliable to directly get the token. You first have to listen to the authentication state change event which fires upon initialization since its asynchronous.
import {auth} from '../utils/firebase'
import {useState, useEffect} from 'react'
export default function useToken() {
const [token, setToken] = useState('')
useEffect(() => {
return auth().onAuthStateChanged(user => {
if (user) {
user.getIdToken(true)
.then(latestToken => setToken(latestToken))
.catch(err => console.log(err))
}
})
}, [])
return token
}
then use like so in your functional component
const token = useToken()
useEffect(() => {
if (token) {
// go wild
}
}, [token])

Related

listUsers is not a function error in Firebase Authentication

I'm trying to fetch a list of all the users in my web app, but I keep receiving this error:
"TypeError: utils_firebase_WEBPACK_IMPORTED_MODULE_2_.auth.listUsers is not a function"
I copied exactly from Firebase documentation, and below is my code.
auth prints out "AuthImpl {app: FirebaseAppImpl, heartbeatServiceProvider: Provider, config: {…}, currentUser: null, emulatorConfig: null, …}", so I know that auth exists.
import { useEffect } from "react";
import { auth } from "../utils/firebase";
function users() {
const listAllUsers = (nextPageToken) => {
console.log(auth);
auth
.listUsers(1000, nextPageToken)
.then((listUsersResult) => {
listUsersResult.users.forEach((userRecord) => {
console.log("user", userRecord);
});
if (listUsersResult.pageToken) {
listAllUsers(listUsersResult.pageToken);
}
})
.catch((error) => {
console.log("Error listing users:", error);
});
};
useEffect(() => {
listAllUsers();
}, []);
return <div>users</div>;
}
export default users;
Can someone help me with this? Thanks!
I tried to look at the documentation from Firebase, but with no luck
listUsers() is a method from the Admin SDK and not from the JS SDK. The page you copied the code from documents the Admin SDK methods for the Authentication Service.
There isn't any corresponding method in the JS SDK because, for security reasons, it is not possible to let a user directly listing all users of a Firebase project from a front-end.
If you want to list all users of your Firebase project from your front-end you can write a Callable Cloud Function that uses the listUsers() Admin SDK's method. It's then up to you to verify who can call this Cloud Function.

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.

Firebase Authentication JS does not populate `providerData`array

in a VueJS / QuasarJS application Im using firebase-js-sdk [1] together with firebaseui-web [2] to handle authentication.
After successful auth with any of the configured providers (e.g. password, google, apple, etc) I want to check which provider the user used. But immediately after successful authentication the user.providerData[] array that should contain the information is empty.
BUT if I reload my app the user.providerData[] array is suddenly populated correctly.
I´m checking for user data with something like this
import { getAuth } from "firebase/auth";
const auth = getAuth();
const user = auth.currentUser;
if (user) {
console.log(user.providerData)
}
After that the user object is fully populated (incl auth tokens, etc) but the user.providerData[] array is empty. Only after a page reload (CTRL-R) does the array get populated.
I searched both projects issues pages and documentation and didnt find anything that could explain this.
Im thankful for ANY idea where to look next!
EDIT
As suggested by #aside Im using onAuthStateChanged to check for updates of the user state.
onAuthStateChanged(
fbAuth,
(user) => {
if (user) {
console.log("onAuthStateChanged: user found");
console.log("onAuthStateChanged: user.providerData", user.providerData);
console.log("onAuthStateChanged: user", user);
} else {
console.log("onAuthStateChanged: no user found");
}
},
function (error) {
console.log("onAuthStateChanged:", error);
}
);
But even if I wait minutes after authentication is completed, still the user.providerData array is only populated after a page reload.
Here is a full demo: https://codesandbox.io/s/github/perelin/firebase-auth-providerdata-test
Thanks in advance :)
Im using
"firebase": "9.6.1",
"firebaseui": "6.0.0",
[1] https://github.com/firebase/firebase-js-sdk
[2] https://github.com/firebase/firebaseui-web
Your app should call getAuth().currentUser.reload() to refresh the local user data after login.
This could be done either in beforeRouteEnter() nav guard of the LoggedIn view:
// LoggedIn.vue
import { getAuth, signOut } from "firebase/auth";
export default {
async beforeRouteEnter(to, from, next) {
await getAuth().currentUser?.reload() 👈
next()
},
}
demo 1
Or in the onAuthStateChanged callback:
// main.js
onAuthStateChanged(
fbAuth,
async (user) => {
await user?.reload() 👈
},
)
demo 2
Your code is only running once instead of running every time the auth state is updated.
If you want to listen to any changes to the auth state, use a callback along with onAuthStateChanged as described here.
https://firebase.google.com/docs/auth/web/manage-users#get_the_currently_signed-in_user
import { getAuth, onAuthStateChanged } from "firebase/auth";
const auth = getAuth();
onAuthStateChanged(auth, (user) => {
if (user) {
// Check used provider here
const providerData = user.providerData;
// ...
} else {
// User is signed out
// ...
}
});
The reason checking/requesting the user object right after authentication does not work is that it might take firebase a second to update the providerData array. signInWithX might therefore return before the property is updated.

React Native Google Signin idToken null

I am using react-native 0.60.5, with firebase authentication (using package react-native-firebase and react-native-google-signin).
Everything looks good to me and the google sign return an object with the user logged in, but the idToken is always null. I need to get the idToken to perform the authentication in firebase.
import { GoogleSignin } from 'react-native-google-signin';
export const googleLogin = async () => {
GoogleSignin.configure();
const userInfo = await GoogleSignin.signIn();
//here we have the issue. userInfo cotains all google user informations except the idToken
//userInfo.idToken is null
}
how can I fix the google-sign-in to return the idToken?
Add the web client id which is availble in Firebase authentication/signInmethod/Google:
function configureGoogleSign() {
GoogleSignin.configure({
webClientId: WEB_CLIENT_ID,
offlineAccess: false
})
}
This is working for me.
You have not specified webclientId.
and make sure to configure google signIn in useEffect Method so when you load the screen it already configured.
useEffect(() => {
GoogleSignin.configure({
webClientId:
'YOUR_WEBCLIENT_ID',
});
}, []);

How to know if Firebase Auth is currently retrieving user?

Background
I am using GoogleAuthProvider, with the default LOCAL persistence.
When I navigate to the page, I do:
firebase.initializeApp(firebaseConfig)
firebase.auth().currentUser // this is always null
firebase.auth().onAuthStateChanged(user => {
console.log("authStateChanged", user)
})
If the user is logged in, the callback is called once, with the user.
If the user is not logged in, the callback is also called once, with null.
This suggests I could wait until the first callback after navigating to the page to get the real login state before deciding what view to display, for instance. (I originally thought that it would not get called with null, and so I could end up waiting indefinitely)
Question
Would that be idiomatic usage? Does it seem like it will be robust against updates to firebase? Where can I find this discussed in the official documentation?
2022 Edit: in firebase web SDK 9, it's
import { getAuth, onAuthStateChanged } from "firebase/auth";
const auth = getAuth();
export const isReady = new Promise(resolve => {
const unsubscribe = onAuthStateChanged(auth, (/* user */) => {
resolve(/* user */)
unsubscribe()
})
})
P.S: The reason I don't resolve with the user is because it is available at auth.currentUser, while the promise would retain an outdated value.
Looking at similar questions such as Pattern for Firebase onAuthStateChanged and Navigation Guards - Quasar app it seems this is indeed the way it's done.
So I have come up with the following to differentiate the initial condition:
export const isReady = new Promise(resolve => {
const unsubscribe = firebase.auth().onAuthStateChanged(() => {
resolve()
unsubscribe()
})
})
I export this Promise from the module where I wrap firebase, so I can begin other initialization while waiting for an authoritative authentication state.
this worked for me instead. NB: For those user Quasar
export default async ({ app, router, store }) => {
return new Promise(resolve => {
const unsubscribe = auth.onAuthStateChanged((user) => {
auth.authUser = user
resolve()
unsubscribe()
})
})
}

Resources