Sign in with Firebase-Admin using node.js - node.js

I am trying to sign in with node.js with firebase-admin, but when I look up there API, they only have sections on update, delete and create.
They do have sections on how to get the user by email but if i want to sign in a user should I not be verifying also by their password as well. I feel like I am incorrectly reading how to use firebase-admin. My best guess is that I should be using straight Firebase and not the new firebase-admin.
Edit:
I only want to sign in the user by email (i.e. not by google sign in or facebook login), if that is possible.

The Firebase Admin Node.js SDK (firebase-admin on npm) is for administrative actions like fetching user data or changing a user's email without their existing password. If you just want to sign in as a user, you should use the Firebase client Node.js SDK (firebase on npm).

Here is the answer in another post:
How to authenticate an user in firebase-admin in nodejs?.
Copy and Paste, just in case:
Install firebase module: npm install firebase --save
const firebase = require("firebase");
const config = {
apiKey: "",
authDomain: "",
databaseURL: "",
projectId: "",
storageBucket: "",
messagingSenderId: ""
};
firebase.initializeApp(config);
exports.login = functions.https.onRequest((req, rsp)=>{
const email = req.body.email;
const password = req.body.password;
const key = req.body.key;
const _key = '_my_key_';
let token = '';
if(key === _key){
firebase.auth().signInWithEmailAndPassword(email,password).then((user)=>{
//The promise sends me a user object, now I get the token, and refresh it by sending true (obviously another promise)
user.getIdToken(true).then((token)=>{
rsp.writeHead(200, {"Content-Type": "application/json"});
rsp.end(JSON.stringify({token:token}));
}).catch((err)=>{
rsp.writeHead(500, {"Content-Type": "application/json"});
rsp.end(JSON.stringify({error:err}));
});
}).catch((err)=>{
rsp.writeHead(500, {"Content-Type": "application/json"});
rsp.end(JSON.stringify({error:err}));
});
} else {
rsp.writeHead(500, {"Content-Type": "application/json"});
rsp.end(JSON.stringify('error - no key'));
}
});

If you are still looking for sign in without using client libraries, here is the approach.
Fire a request to the following url's based on the required action
Signup : https://www.googleapis.com/identitytoolkit/v3/relyingparty/signupNewUser
Signin : https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyPassword
This allows to create/signin users by firing a http request to the above urls. You get the required tokens and these are compatible with firebase. I assume firebase internally uses these url's in the client libraries.
Hope its helpful!

Related

How to verify Google signin (via Firebase) idToken in nodejs backend?

Trying to verify idToken of a user signed in via firebase authentication (Google signin) in nodejs server. Server throws Firebase ID token has invalid signature.
Tried verifying with firebase-admin as well as jsonwebtoken with public key from the url: https://www.googleapis.com/robot/v1/metadata/x509/securetoken#system.gserviceaccount.com. Both methods work perfect for users signed in with a password, but throws 'Invalid Signature' in case of a user signed in via google.
Is there anything I am doing wrong? Do I need to verify with google-auth-library instead?
Code:
import * as admin from "firebase-admin";
admin.initializeApp({
credential: admin.credential.cert(require("../../serviceAccount")), // file received from firebase project settings page
databaseURL: "as mentioned in the firebase project settings page",
});
// Some code here
var token = "token received from client side";
var decoded = await admin.auth().verifyIdToken(token);
PS:
All client side features (after signing in) are working fine.
Everything else on the backend is working fine.
Decoding the token in both cases gives expected JSON.
For test run, token is being forceRefreshed everytime before calling the API.
OP here,
I am dumb.
I was using the print() function of flutter to log the token and call the API myself. Didn't know Flutter's print function has an output character limit. Login using password gives smaller tokens thus the whole token was logged. But Google sign in gives a longer token, longer than the output character limit of print.
Solution : Use log function from 'dart:developer' package.
import 'dart:developer';
//
log(await _auth.idToken);
const { OAuth2Client } = require("google-auth-library");
const client = new OAuth2Client(googleClient[process.env.ENV])
let token = 123456789011-crhch2kuum79bk0qr3usa39f7b9chikc.apps.googleusercontent.com
async function googleLoginVerify(token) {
try {
const ticket = await client.verifyIdToken({
idToken: token,
audience: googleClient[process.env.ENV],
});
const payLoad = ticket.getPayload();
return {
success: true,
data: payLoad,
};
} catch (err) {
console.log(err.message);
return {
success: false,
message: err.message,
};
}
}

How to get an idToken that is valid for development from firebase without having to spin up my frontend?

I am working on some firebase functions. This one will check if an user is logged in in firebase first. However this is a bit of a hassle in development. Now I need to login on the frontend first to get the id_token, pass it to my function url, then see the result.
The process I am following is described in the official docs: https://firebase.google.com/docs/auth/admin/verify-id-tokens
node.js
const admin = require('firebase-admin');
admin.initializeApp();
module.exports = function( request, response ) {
if( !request.query.id_token )
response.status(400).json({message: 'id token has not been provided'});
admin.auth()
.verifyIdToken( request.query.id_token )
.then( token => {
// TODO: redirect to payment portal
return response.status(200).json({message: 'Success'});
})
.catch( error => {
return response.status(401).json({message: 'You are currently not logged in as an authorised user'});
})
}
Is there a way to get an id_token that is valid from firebase without having to spin up my frontend? Good and simple alternatives solutions are welcome too.
NOTE: I am using the firebase emulators during development.
Since you're using the Firebase emulators you may create a fake user and retrieve an id token programmatically. The code below creates and logs in a user and returns an id_token that will be accepted by your function.
var firebase = require("firebase/app");
require("firebase/auth");
// Initialize Firebase and connect to the Authentication emulator
var firebaseConfig = {
// Insert Firebase config here
};
firebase.initializeApp(firebaseConfig);
firebase.auth().useEmulator('http://localhost:9099/');
// Create a fake user and get the token
firebase.auth().createUserWithEmailAndPassword("example#example.com", "password")
.then((userCredential) => {
console.log("User created")
});
firebase.auth().signInWithEmailAndPassword("example#example.com", "password")
.then((userCredential) => {
console.log("User logged in")
userCredential.user.getIdToken().then((idToken) => {
console.log(idToken)
});
});

How to send email verification when registering a new user in Firebase? [duplicate]

In the past, I have used firebase.auth in the web client and once a user creates another user, I link certain security logic:
Once the user has been created I send an email to verify your email
with the function user.sendEmailVerification ().
As the user was created by another user, I assign a default password
and use the sendPasswordResetEmail () function so that the user
registers his new password.
That has worked well for me so far, but now for many reasons I need to move that logic to my server, for that I'm developing a backend with cloud functions and I'm using the Node.js Firebase Admin SDK version 6.4.0, but I can not find a way to use the functions of user.sendEmailVerification() and sendPasswordResetEmail() to implement the same logic on the server, the closest thing I found was:
auth.generateEmailVerificationLink (email)
auth.generatePasswordResetLink (email)
But it only generates a link for each one, which by the way the only emailVerification() serves me, the one from generatePasswordReset always tells me:
Try resetting your password again
Your request to reset your password has expired or the link has
already been used.
Even though be a new link, and it has not been used.
My 3 questions would be:
How can I make the sendEmailVerification () and
sendPasswordResetEmail () functions work on the server?
How can I make the link generated with
auth.generatePasswordResetLink (email) work correctly on the server?
Is there any way to use templates and emails on the server that are
in firebase auth?
Thank you in advance for sharing your experience with me, with all the programmers' community of stack overflow.
Those functions are not available in firebase-admin, but you should be able to run the client-side SDK (firebase) on the server as well. Not exactly a best practice, but it will get the job done. There's a long standing open feature request to support this functionality in the Admin SDK. You will find some helpful tips and workarounds there.
Could be a bug. I would consider reporting it along with a complete and minimal repro. The Admin SDK does have an integration test case for this use case, but it works slightly differently.
Not at the moment. Hopefully, this will be covered when the above feature request is eventually fulfilled.
The is a workaround provided here
https://github.com/firebase/firebase-admin-node/issues/46
I found a work-around that works well enough for my use case, see below. I'm not sure if this is best practice, but I wanted to keep the emails exactly the same between the server and client requests. Would love to hear about any flaws with this implementation 💡
As suggested above, it uses a three step process to do this:
Acquire a custom token via the admin sdk's createCustomToken(uid)
It converts this custom token to an idToken via the API
It invokes the send email verification endpoint on the API
const functions = require('firebase-functions');
const fetch = require('node-fetch');
const admin = require('firebase-admin');
const apikey = functions.config().project.apikey;
const exchangeCustomTokenEndpoint = `https://identitytoolkit.googleapis.com/v1/accounts:signInWithCustomToken?key=${apikey}`;
const sendEmailVerificationEndpoint = `https://identitytoolkit.googleapis.com/v1/accounts:sendOobCode?key=${apikey}`;
module.exports = functions.auth.user().onCreate(async (user) => {
if (!user.emailVerified) {
try {
const customToken = await admin.auth().createCustomToken(user.uid);
const { idToken } = await fetch(exchangeCustomTokenEndpoint, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
token: customToken,
returnSecureToken: true,
}),
}).then((res) => res.json());
const response = await fetch(sendEmailVerificationEndpoint, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
requestType: 'VERIFY_EMAIL',
idToken: idToken,
}),
}).then((res) => res.json());
// eslint-disable-next-line no-console
console.log(`Sent email verification to ${response.email}`);
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
}
}
});
I'm sure it doesn't matter anymore, but I had a headache doing this so I'd like to share even if it isn't the greatest answer.
await admin.auth().createUser(
{email, password, displayName, phoneNumber, photoURL}
).then(function(userRecord) {
admin.auth().createCustomToken(userRecord.uid).then(function(customToken){
createdToken=customToken;
firebase.auth().signInWithCustomToken(createdToken).catch(function(error){
return console.log(error)
})
firebase.auth().onAuthStateChanged(function(user) {
user.sendEmailVerification().then(function(){
return console.log('It worked')
},function(error) {
return console.log(error)
})
});
})
})

Firebase auth with NodeJS: getting the UID of the client making the requests

I use NodeJS to communicate with Firebase's Realtime Database.
The rules are defined so only an admin account can read and write:
{
"rules": {
".read": "auth.uid === 'ADMIN_UID'",
".write": "auth.uid === 'ADMIN_UID'"
}
}
When the NodeJS Express server initializes I log in with the admin credentials.
On the web application I use Firebase's "Login with facebook" option:
const provider = new firebase.auth.FacebookAuthProvider();
provider.setCustomParameters({
display: "popup",
});
firebase
.auth()
.signInWithPopup(provider)
Everything works perfectly.
Now, I am trying to extract the User Id (uid) of the user making the requests from the Web Application. When I run the following line:
const user = firebase.auth().currentUser;
I get the admin user rather than the user making the requests. This is expected I guess. My question is how do I get the UserId without sending it explicitly to avoid a security concern?
I've managed to accomplish this with Firebase Admin SDK.
As described in this guide, you'd need to:
Generate a private key for your service account
In the Firebase console, open Settings > Service Accounts.
Click Generate New Private Key, then confirm by clicking Generate Key.
Securely store the JSON file containing the key.
Set the environment variable
Add to your .bashrc or .zshrc:
export GOOGLE_APPLICATION_CREDENTIALS="/home/user/Downloads/service-account-file.json"
- In NodeJS initialize the SDK:
admin.initializeApp({
credential: admin.credential.applicationDefault(),
databaseURL: 'https://<DATABASE_NAME>.firebaseio.com'
});
In your client send the JWT token in the header:
const token = await firebase.auth().currentUser.getIdToken()
fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: token
},
body: ...
});
In your server decrypt the token and get the UID:
return admin
.auth()
.verifyIdToken(token)
.then(function(decodedToken) {
var uid = decodedToken.uid;
console.log("uid ->", uid);
return uid;
})
.catch(function(error) {
console.log("error ->", error);
// Handle error
});
That's it. Apparently working with Firebase via your own NodeJS domain is called "using your custom backend". This means the default usage for them is without a NodeJS or other backend middleman.
Usually with firebase you can include access the context of the request being made and the userId is available.
Something like the following:
context.params.userId

Firebase authentication (is not a function, is not a constructor)

I don't know what is wrong. I'm using Node.js and trying to log in using email/password and Google authentication. I have enabled all of them in Firebase console.
npm Firebase version - 3.1.0
part of code:
var firebase = require('firebase');
var config = {
apiKey: "AIzaSyAH27JhfgCQfGmoGTdv_VaGIaX4P-qAs_A",
authDomain: "pgs-intern.firebaseapp.com",
databaseURL: "https://pgs-intern.firebaseio.com",
storageBucket: "pgs-intern.appspot.com",
};
firebase.initializeApp(config);
app.post('/login', function(req, res) {
var auth = firebase.auth();
firebase.auth().signInWithEmailAndPassword(req.body.login, req.body.password).catch(function(error) {
// Handle Errors here.
var errorCode = error.code;
var errorMessage = error.message;
// ...
});
}
Error: firebase.auth(...).signInWithLoginAndPassword is not a function
or
Error: firebase.auth(...).GoogleAuthProviders is not a constructor when I write
firebase.auth().signInWithPopup(provider).then(function(result) {
// This gives you a Google Access Token. You can use it to access the Google API.
var token = result.credential.accessToken;
// The signed-in user info.
var user = result.user;
// ...
}).catch(function(error) {
// Handle Errors here.
var errorCode = error.code;
var errorMessage = error.message;
// The email of the user's account used.
var email = error.email;
// The firebase.auth.AuthCredential type that was used.
var credential = error.credential;
// ...
});
I just did exactly what is in documentation.
Your first error probably comes from a typo somewhere.
firebase.auth(...).signInWithLoginAndPassword is not a function
Notice it says signInWithLoginAndPassword, the function is called signInWithEmailAndPassword. In the posted code it's used correctly, so it's probably somewhere else.
firebase.auth(...).GoogleAuthProviders is not a constructor
You have not posted the code where you use this, but I assume this error happens when you create your provider variable, that you use in firebase.auth().signInWithPopup(provider)
That line should be var provider = new firebase.auth.GoogleAuthProvider();
Based on the error message, I think you might be doing new firebase.auth().GoogleAuthProvider(); Omit the brackets after auth, if that's the case.
Do not call GoogleAuthProvider via an Auth() function.
According to the documentation you have to create an instance of GoogleAuthProvider.
let provider = new firebase.auth.GoogleAuthProvider()
Please check the following link https://firebase.google.com/docs/auth/web/google-signin
There is no way to sign your node.js app into firebase with email+password or one of the social providers.
Server-side processes instead sign into Firebase using so-called service accounts. The crucial difference is in the way you initialize the app:
var admin = require('firebase-admin');
admin.initializeApp({
serviceAccount: "path/to/serviceAccountCredentials.json",
databaseURL: "https://databaseName.firebaseio.com"
});
See this page of the Firebase documentation for details on setting up a server-side process.

Resources