How to authenticate an user in firebase-admin in nodejs?
Asked Answered
P

4

38

At the moment I am creating a Firebase API on nodejs. I would like to handle all Firebase stuff (like authentication) with firebase-admin on nodejs. But what is the correct way to authenticate a user over nodejs in firebase-admin without the Javascript Firebase SDK on the client side? On the official documentation for admin I didn't find a function called signInWithEmailAndPassword (like as on the client side SDK) for nodejs. There is only a function called: "getUserByEmail", but this function doesn't check if the user has entered the correct password.

This is my form:

<form class="sign-box" action="/login" method="post">

      <div class="form-group">
          <input id="username" name="username" type="text" class="form-control" placeholder="E-Mail"/>
      </div>
      <div class="form-group">
          <input id="password" name="password" type="password" class="form-control" placeholder="Password"/>
      </div>
      <button type="submit" class="btn btn-rounded">Sign in</button>

</form>

Once the form is submitted I pass the values to my API in nodejs:

app.post('/login', urlencodedParser, function (req, res) {

    // getting the values

    response = {
        username: req.body.username,
        password: req.body.password

    };    

    // authenticate the user here, but how ?

});

My first idea was to use the Firebase SDK on the client side to sign in with signInWithEmailAndPassword and to get the uid. Once I had the UID I wanted to sent the UID to nodejs and call the function createCustomToken and to return the generated token (with some additional claims) back to the client. Once I get the token back I would use the function signWithCustomToken (on the client side) to authenticate the user. Is this way correct or is there a better way ?

Philippines answered 4/7, 2017 at 7:31 Comment(2)
Typically, the user is authenticated on the client side. Firebase is optimized and designed to run on the client side. Anyway, you can use the client node.js require('firebase') library on your server if you insist. That has the client side APIs you need signInWithEmailAndPassword, etc.Steer
Yes thats true. The reason why I am using firebase-admin is that I can add custom claims to the token and send it back to the user. But this is a good approach to use firebase module in nodejs, to authenticate and send the token back. Thanks!Philippines
B
27

Actually for authentication you will need to use the firebase regular api, no the admin.

First this will give you a refreshed firebase token, not a custom token. If you like you can do the same to obtain a custom token, if you need a custom token, I also have an example.

npm install firebase --save

const firebase = require("firebase");
const config = {
    apiKey: "",
    authDomain: "",
    databaseURL: "",
    projectId: "",
    storageBucket: "",
    messagingSenderId: ""
};
firebase.initializeApp(config);

I am posting my login firebase function but you will be able to change it to express easily.

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'));
    }
});

NOTE: I am using this login function to test my other functions with Postman, so that is why i am sending a key, so I can use this privately.

Now combining the ADMIN and FIREBASE node apy I am able to do a lot of very interesting stuff with HTTP functions on my firebase.

Hope it helps somehow.

Benzel answered 25/7, 2017 at 15:32 Comment(4)
The custom token example would be of much help. I did not know using firebase.auth() would work in functions as well!Pampas
Are you having trouble to make it work? I mean using a custom token? that one took me a while to understand.Benzel
If you get getIdToken() is not a function it's because they updated the firebase lib with another breaking change. Now you need to destructure the user like so: .then(({ user })=>{ ...Viscountess
I had no idea you could use the client SDK on the server, is this even in the docs? Anyway thanks a lot for this. Noob question, but what is the purpose of that token you return back to the client? This example lacks the frontend implementation.Carmelcarmela
H
5

The officially recommended and supported way is to use ID TOKENS

From the official docs:

If your Firebase client app communicates with a custom backend server, you might need to identify the currently signed-in user on that server. To do so securely, after a successful sign-in, send the user's ID token to your server using HTTPS. Then, on the server, verify the integrity and authenticity of the ID token and retrieve the uid from it. You can use the uid transmitted in this way to securely identify the currently signed-in user on your server.

The workflow is:

  1. Use the Firebase Web SDK in your client
  2. The user logs in with any of the authentication methods
  3. Retrieve the ID token on the client
firebase.auth().currentUser.getIdToken(/* forceRefresh */ true).then(function(idToken) {
  // Send token to your backend via HTTPS
  // ...
}).catch(function(error) {
  // Handle error
});
  1. Send this token to the server
  2. Server verifies the ID token with the Firebase Admin SDK
// idToken comes from the client app
getAuth()
  .verifyIdToken(idToken)
  .then((decodedToken) => {
    const uid = decodedToken.uid;
    // ...
  })
  .catch((error) => {
    // Handle error
  });
  1. your user is securely authenticated and uniquely identified
Histamine answered 25/10, 2022 at 13:3 Comment(2)
do you know if it’s possible to do the reverse? create a user server side (upon checkout) then login a web user with a server token?Akira
that is the real question...Olva
S
2

For Any Server Side React Users I was brought here because I was attempting to authenticate users in firebase without the Javascript Firebase SDK on the client side as well. I am building a server side rendered react app. The client-side firebase.auth() does not work on a server-side node environment.

It turns out that you can run firebase.auth() commands inside of componentDidMount(), because that does not run on the server. This is where you can authenticate and get your user's token, and then send it to a cloud function for any server-side rendering that requires user authentication.

On the server side, you can then verify the token with the admin sdk.

You will also need to require firebase/app and firebase/auth, and initialize firebase in your browser-specific bundle.js, so that it is not included in your server's bundle.js

componentDidMount() {
    firebase.auth().onAuthStateChanged(function(user) {
        if (user) {
            console.log("User signed in!");
        } else {
            console.log("User NOT signed in!");
        }
    });
}
Southwestwardly answered 29/1, 2018 at 15:10 Comment(0)
C
1

This is my solution, maybe it can help someone (Node/react). For some reason the client side method signInWithEmailAndPassword seems to work both on the client AND server. Basically this lets you keep the default security rule ".read": "auth != null" without having to use signInAnonymously() hence avoid creating an infinite number of stale users.

server:

const { firebase } = require('../../firebase/frontend');
const { firebase: admin } = require('../../firebase/backend');

const email = process.env.READ_ONLY_EMAIL;
const password = process.env.READ_ONLY_PASSWORD;

export default async (req, res) => {
  try {
    const { user } = await firebase.auth().signInWithEmailAndPassword(email, password);
    const customToken = await admin.auth().createCustomToken(user.uid);
    return res.status(200).send(JSON.stringify(customToken));
  } catch (error) {
    return res.status(404).send(error);
  }
};

client:

import fetch from 'isomorphic-unfetch';
import { useState, useEffect } from 'react';
import { firebase } from '../firebase/frontend';

const useUser = (props) => {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);
  const [isAnonymous, setIsAnonymous] = useState(true);

  const getCustomToken = async () => {
    const response = await fetch('/api/auth', { method: 'POST' });
    const json = await response.json();
    return json;
  };

  useEffect(() => {
    try {
      const unsubscribe = firebase.auth().onAuthStateChanged(async (user) => {
        // user exists
        if (user && user.email !== '[email protected]') {
          setUser(user);
          setIsAnonymous(false);
          // else sign in user "anonymously"
        } else {
          setIsAnonymous(true);
          const token = await getCustomToken();
          firebase.auth().signInWithCustomToken(token);
        }
        setLoading(false);
      });
      return () => unsubscribe();
    } catch (error) {
      console.log('Error signing in user', error);
    }
  }, []);

  return {
    user,
    isAnonymous,
    loading
    // etc...
  };
};

export default useUser;
Carmelcarmela answered 19/7, 2021 at 12:36 Comment(3)
Hi, i am trying to implement a custom authentication system with firebase and nodejs. Indeed it seems that we need the client sdk inside nodejs environment to compliment the admin skd as it does not support all operations, like matching an email with a password, so we need to call the signInWithEmailAndPassword() before we issue a custom token. However is it ok to do that? the documentation states that the admin sdk is suitable for nodejs while the js sdk is suitable for client side apps.Maximo
@GiannisSavvidis it works for us without any problem! I'm not sure if it's the "correct" way to do it or not though. But it certainly works.Carmelcarmela
If you are sending the user's password to your server then try to find another way.Pleadings

© 2022 - 2024 — McMap. All rights reserved.