I have a React App hosted in Firebase. I'm tring to get "lastLoginAt" information of the user. I can get this via Firebase Auth Rest API "Get user data" but problem is FIREBASE_ID_TOKEN is needed at sample request.
curl 'https://identitytoolkit.googleapis.com/v1/accounts:lookup?key=[API_KEY]' \
-H 'Content-Type: application/json' --data-binary '{"idToken":"[FIREBASE_ID_TOKEN]"}'
In order to get FIREBASE_ID_TOKEN, user have to be logged in but then this overwrites "lastLoginAt" value and makes it useless for my case. Maybe I should look for another data elsewhere but I'm not very experienced in Firebase.
Another thing I tried was trying to import firebase-admin to my app, but as it is a browser based client side app it is not possible. Then for the last I authenticated a user via this separate js standalone module which worked fine;
import admin from "firebase-admin"
import serviceAccount from "./serviceAccountKey.json" assert { type: "json" } // Initialize the Firebase Admin SDK with your project credentials
admin.initializeApp({
credential : admin.credential.cert( serviceAccount ),
databaseURL : 'DATABASE_URL'
});
// Get a reference to the Firebase Authentication user management API
const auth = admin.auth()
// Set custom claims for the user that grant the necessary permissions to retrieve user information
const uid = 'UID_USER' // The UID of the user to authorize
const customClaims = {
authorizedUser: true // A custom claim that grants permission to retrieve user information
}
auth.setCustomUserClaims( uid, customClaims)
.then(() => {
console.log( 'User authorized successfully' );
})
.catch(error => {
console.error( error );
})
With this authorized user I tried to grab last login time of users without these users logging in and overwriting their lastloginat values.
The curl API call I tried was;
curl --location --request POST 'https://identitytoolkit.googleapis.com/v1/accounts:lookup?key=APP_KEY' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer LOOOONG_TOKEN'
Finally this gave me following reply;
{
"error": {
"code": 401,
"message": "Request had invalid authentication credentials. Expected OAuth 2 access token, login cookie or other valid authentication credential. See https://developers.google.com/identity/sign-in/web/devconsole-project.",
"errors": [
{
"message": "Request had invalid authentication credentials. Expected OAuth 2 access token, login cookie or other valid authentication credential. See https://developers.google.com/identity/sign-in/web/devconsole-project.",
"domain": "global",
"reason": "unauthorized"
}
],
"status": "UNAUTHENTICATED"
}
}
I know I can do this in another way by recording login times to a DB table and fetching them but knowing that this data is already available tickles me to do it trough Firebase. Anyone has an idea what else can I try or what I am doing wrong ?
If you are looking to track when the user was last active, take a look at this thread.
If you are looking to track when users actually log in, you'll need to write to the RTDB/Firestore immediately after a call to a signIn*
method with the value of new Date(currentUser.metadata.lastSignInTime).getTime()
(reference: Legacy SDK / Modern SDK). As long as that user doesn't need to call another signIn
method, their old lastLoginAt
time won't change.
// Legacy syntax
import * as firebase from "firebase/app";
import "firebase/auth";
import "firebase/database";
const auth = firebase.auth();
const rtdb = firebase.database();
const { user } = await auth.signInWithEmailAndPassword(email, password);
await rtdb.ref("users/" + user.uid + "/lastLoginAt")
.set(new Date(user.metadata.lastSignInTime).getTime());
// Modern syntax
import { getAuth, signInWithEmailAndPassword } from "firebase/auth";
import { getDatabase, ref, set } from "firebase/database";
const auth = getAuth();
const rtdb = getDatabase();
const { user } = await signInWithEmailAndPassword(auth, email, password);
await set(
ref(rtdb, "users/" + user.uid + "/lastLoginAt"),
new Date(user.metadata.lastSignInTime).getTime()
);
If the intent is to track how many days ago the user last logged in, instead of using a basic set
operation, update the new login time using a transaction:
// Legacy syntax
import * as firebase from "firebase/app";
import "firebase/auth";
import "firebase/database";
const auth = firebase.auth();
const rtdb = firebase.database();
const { user } = await auth.signInWithEmailAndPassword(email, password);
let previousLoginTime;
await rtdb.ref("users/" + user.uid + "/lastLoginAt")
.transaction((serverPreviousLoginTime) => {
previousLoginTime = serverPreviousLoginTime;
return new Date(user.metadata.lastSignInTime).getTime();
});
// previousLoginTime now contains the previous login, in milliseconds, if any (it can be null).
if (previousLoginTime) {
console.log("User's last login: " + String(new Date(previousLoginTime)));
}
// Modern syntax
import { getAuth, signInWithEmailAndPassword } from "firebase/auth";
import { getDatabase, ref, runTransaction } from "firebase/database";
const auth = getAuth();
const rtdb = getDatabase();
const { user } = await signInWithEmailAndPassword(auth, email, password);
let previousLoginTime;
await runTransaction(
ref(rtdb, "users/" + user.uid + "/lastLoginAt"),
(serverPreviousLoginTime) => {
previousLoginTime = serverPreviousLoginTime;
return new Date(user.metadata.lastSignInTime).getTime();
});
);
// previousLoginTime now contains the previous login, in milliseconds, if any (it can be null).
if (previousLoginTime) {
console.log("User's last login: " + String(new Date(previousLoginTime)));
}
If the intent is to track recent login sessions, you can alternatively push data to the database rather than overwrite it.
// Legacy syntax
import * as firebase from "firebase/app";
import "firebase/auth";
import "firebase/database";
const auth = firebase.auth();
const rtdb = firebase.database();
const { user } = await auth.signInWithEmailAndPassword(email, password);
await rtdb.ref("userLogins/" + user.uid)
.push({
timestamp: new Date(user.metadata.lastSignInTime).getTime()
/* ... other data? ... */,
});
// Modern syntax
import { getAuth, signInWithEmailAndPassword } from "firebase/auth";
import { getDatabase, ref, push } from "firebase/database";
const auth = getAuth();
const rtdb = getDatabase();
const { user } = await signInWithEmailAndPassword(auth, email, password);
await push(
ref(rtdb, "userLogins/" + user.uid),
{
timestamp: new Date(user.metadata.lastSignInTime).getTime()
/* ... other data? ... */,
}
);
With the release of Cloud Functions v2, you also create a beforeUserSignedIn
function to handle the same logic discussed above. In most situations, the code above can be reused by swapping out "firebase/*"
for "firebase-admin/*"
.