如何在Node.js中使用Firebase-Admin进行用户身份验证?

35

目前我正在创建一个基于nodejs的Firebase API。 我想使用firebase-admin在nodejs上处理所有Firebase相关内容(例如身份验证)。 但是,在不使用客户端Javascript Firebase SDK的情况下,在nodejs上验证用户的正确方法是什么? 在官方管理文档中,我没有找到名为signInWithEmailAndPassword(类似于客户端SDK)的函数。只有一个名为:“getUserByEmail”的函数,但该函数不检查用户是否输入了正确的密码。

这是我的表单:

<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>

当表单提交后,我将值传递到我的nodejs API中:

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 ?

});

我的第一个想法是在客户端使用Firebase SDK通过signInWithEmailAndPassword 进行登录,然后获取uid。一旦我有了UID,我想将UID发送到nodejs并调用createCustomToken函数,返回生成的令牌(带有一些其他声明)给客户端。一旦我收到令牌,我将使用 signWithCustomToken函数(在客户端)对用户进行身份验证。这种方式正确吗?还是有更好的方法?


3
通常情况下,用户在客户端进行身份验证。Firebase被优化和设计为在客户端运行。不过,如果您坚持要这样做,您可以在服务器上使用客户端的Node.js require('firebase')库。该库具有您所需的客户端API,例如signInWithEmailAndPassword等。 - bojeil
是的,这是真的。我使用firebase-admin的原因是可以向令牌添加自定义声明并将其发送回用户。但在Node.js中使用Firebase模块进行身份验证并发送令牌也是一种好方法。谢谢! - Harry
4个回答

23

实际上,用于认证的应该使用Firebase常规API,而不是管理员API。

首先,这将给您一个刷新的Firebase令牌,而不是自定义令牌。如果需要自定义令牌,则可以执行相同的操作以获取自定义令牌,我还有一个示例。

npm install firebase --save

const firebase = require("firebase");
const config = {
    apiKey: "",
    authDomain: "",
    databaseURL: "",
    projectId: "",
    storageBucket: "",
    messagingSenderId: ""
};
firebase.initializeApp(config);
我将发布我的登录firebase函数,但是您可以轻松地将其更改为express。
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'));
    }
});

注意:我正在使用此登录功能在Postman中测试我的其他功能,这就是为什么我会发送一个密钥,这样我可以私下使用它。

现在结合ADMIN和FIREBASE节点apy,我能够在我的Firebase上使用许多非常有趣的HTTP函数。

希望它在某种程度上有所帮助。


自定义令牌示例将非常有帮助。 我不知道在函数中使用firebase.auth()也可以工作! - Riël
你在使用自定义令牌时遇到了问题吗?那个东西我也花了一段时间才理解。 - Pablo Palacios
1
如果你得到了 getIdToken() is not a function 的错误,那是因为 Firebase 库进行了另一个破坏性的更新。现在你需要像这样解构 user.then(({ user })=>{ ... - corysimmons
我从来不知道你可以在服务器上使用客户端SDK,这甚至在文档中有提到吗?无论如何,非常感谢。作为新手的问题,那个令牌你返回给客户端有什么用途?这个例子缺少前端实现。 - Operator

3

官方推荐和支持的方法是使用ID TOKENS

来自官方文档

如果您的 Firebase 客户端应用程序与自定义后端服务器通信,则可能需要在该服务器上标识当前已登录的用户。为了安全地这样做,在成功登录后,使用 HTTPS 将用户的 ID token 发送到您的服务器。然后,在服务器上验证 ID token 的完整性和真实性,并从中检索 uid。您可以使用以这种方式传输的 uid 来安全地标识当前已登录的用户。

工作流程如下:

  1. 在客户端中使用Firebase Web SDK
  2. 用户使用任何身份验证方法登录
  3. 在客户端检索ID令牌
firebase.auth().currentUser.getIdToken(/* forceRefresh */ true).then(function(idToken) {
  // Send token to your backend via HTTPS
  // ...
}).catch(function(error) {
  // Handle error
});
  1. 将此令牌发送到服务器
  2. 使用Firebase Admin SDK验证ID令牌
// idToken comes from the client app
getAuth()
  .verifyIdToken(idToken)
  .then((decodedToken) => {
    const uid = decodedToken.uid;
    // ...
  })
  .catch((error) => {
    // Handle error
  });

您的用户已通过安全认证并得到唯一标识。

你知道是否有可能反过来做吗?在结账时在服务器端创建一个用户,然后使用服务器令牌登录Web用户? - Crashalot

2
对于任何服务器端的React用户
我来到这里是因为我正在尝试在客户端没有JavaScript Firebase SDK的情况下在Firebase中验证用户。我正在构建一个服务器端渲染的React应用程序。客户端的firebase.auth()在服务器端的节点环境中无法工作。
事实证明,您可以在componentDidMount()内运行firebase.auth()命令,因为它不在服务器上运行。这是您可以进行身份验证并获取用户令牌的地方,然后将其发送到云函数以进行需要用户身份验证的服务器端呈现。
在服务器端,您可以使用admin SDK验证令牌。
您还需要require firebase/app和firebase/auth,并在浏览器特定的bundle.js中初始化firebase,以便它不包含在您的服务器的bundle.js中。

componentDidMount() {
    firebase.auth().onAuthStateChanged(function(user) {
        if (user) {
            console.log("User signed in!");
        } else {
            console.log("User NOT signed in!");
        }
    });
}


1
这是我的解决方案,也许可以帮助某些人(Node/react)。由于某种原因,客户端方法 signInWithEmailAndPassword 似乎在客户端和服务器上都有效。基本上,这使您可以保持默认的安全规则 ".read": "auth != null",而无需使用 signInAnonymously(),从而避免创建无数过期用户。 < p > 服务器:

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

客户端:

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 !== 'readonly@myEmailAddress.com') {
          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;

你好,我正在尝试使用Firebase和Node.js实现自定义身份验证系统。实际上,似乎我们需要在Node.js环境中使用客户端SDK来补充管理SDK,因为它不支持所有操作,比如将电子邮件与密码匹配,所以我们需要在发出自定义令牌之前调用signInWithEmailAndPassword()。但是这样做可以吗?文档说明管理SDK适用于Node.js,而JS SDK适用于客户端应用程序。 - Giannis Savvidis
1
@GiannisSavvidis 对我们来说可以正常工作!但我不确定这是否是“正确”的方式。但它肯定可以工作。 - Operator
如果您正在将用户密码发送到服务器,请尝试找到另一种方法。 - Michael Bushe

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接