【问题标题】:CustomToken not working when issued through "createCustomToken" method通过“createCustomToken”方法发布时,CustomToken 不起作用
【发布时间】:2020-03-04 13:46:15
【问题描述】:

我正在为我的应用程序使用 Firebase 身份验证,并使用它通过 JWT 令牌向后端 API 验证用户。在 API 后端,我配置了 JWT-secret,它是从这个 url 中提取的非对称密钥:

https://www.googleapis.com/service_accounts/v1/jwk/securetoken@system.gserviceaccount.com

这一切都很好。我最近需要创建一个云函数,它也需要调用 API 后端。为此,我正在使用该功能来创建此处找到的自定义令牌:

https://firebase.google.com/docs/auth/admin/create-custom-tokens

这会使用正确的自定义声明创建我的令牌

          let additionalClaims = {
             'x-hasura-default-role': 'admin',
             'x-hasura-allowed-roles': ['user', 'admin']
           }     

        admin.auth().createCustomToken(userId,additionalClaims).then(function (customToken) {
        console.log(customToken);

        response.end(JSON.stringify({
          token: customToken
        }))
      })
      .catch(function (error) {
       console.log('Error creating custom token:', error);
    });

但是,当我尝试将它用于后端 API 时,我收到“JWTInvalidSignature”错误。在我的云功能中,我指定了我的 firebase 项目中的服务帐户,但它似乎没有帮助。当我查看解码的两个令牌时,它们肯定来自不同的服务。

自定义令牌

     {
      "aud": 
       "https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit",
       "iat": 1573164629,
       "exp": 1573168229,
       "iss": "firebase-adminsdk-r2942@postgrest-b4c8c.iam.gserviceaccount.com",
       "sub": "firebase-adminsdk-r2942@postgrest-b4c8c.iam.gserviceaccount.com",
       "uid": "mikeuserid",
        "claims": {
           "x-hasura-default-role": "admin",
           "x-hasura-allowed-roles": [
           "user",
           "admin"
        ]
       }
    }

来自 FireBase 身份验证的令牌

 {
      "role": "webuser",   
      "schema": "customer1",
      "userid": "15",
      "claims": {
      "x-hasura-default-role": "user",
      "x-hasura-allowed-roles": [
      "user",
      "admin"
  ],
    "x-hasura-user-id": "OS2T2rdkM5UlhfWLHEjNExZ71lq1",
    "x-hasura-dbuserid": "15"
   },
   "iss": "https://securetoken.google.com/postgrest-b4c8c",
   "aud": "postgrest-b4c8c",
   "auth_time": 1573155319,
   "user_id": "OS2T2rdkM5UlhfWLHEjNExZ71lq1",
   "sub": "OS2T2rdkM5UlhfWLHEjNExZ71lq1",
   "iat": 1573164629,
   "exp": 1573168229,
   "email": "johnny1@gmail.com",
   "email_verified": false,
   "firebase": {
    "identities": {
     "email": [
    "johnny1@gmail.com"
  ]
  },
    "sign_in_provider": "password"
  }
 }

如何让这个 customToken 与我配置的现有 JWT 密钥一起使用??

【问题讨论】:

    标签: firebase google-cloud-platform google-cloud-firestore firebase-authentication google-cloud-functions


    【解决方案1】:

    Firebase Authentication: Users in Firebase Projects: Auth tokens 中所述,来自 Firebase Auth 的令牌和 Admin SDK 自定义令牌不相同,彼此不兼容并且验证方式不同。


    澄清后编辑回复:

    当您尝试将云函数实例识别为第三方 API 的授权调用者时,您可以使用两种方法。

    在以下两种方法中,您将在每个示例中使用 postToApi('/saveUserData', { ... }); 调用您的 API。您可能还可以组合/支持这两种服务器端方法。

    方法一:使用公私钥对

    对于此版本,我们使用 JSON Web 令牌来证明调用来自 Cloud Functions 实例。在这种形式的代码中,'private.key' 文件与您的函数一起部署,它的公钥保存在您的第三方服务器上。如果您非常频繁地调用您的 API,请考虑将“private.key”文件缓存在内存中,而不是每次都读取它。

    如果您希望使此密钥无效,则必须重新部署所有使用它的函数。或者,您可以修改 fileRead() 调用并将其存储在 Firebase 存储中(secure it - 无人可读,后端管理员可写)。这将允许您通过简单地替换文件来定期刷新私钥。

    • 优点:只有一个远程请求
    • 缺点:更新密钥可能很棘手
    const jwt = require('jsonwebtoken');
    const rp = require('request-promise-native');
    const functionsAdminId = 'cloud-functions-admin';
    
    function getFunctionsAuthToken(jwtOptions) {
      jwtOptions = jwtOptions || {};
      return new Promise((resolve, reject) => {
        // 'private.key' is deployed with function
        fs.readFile('private.key', 'utf8', (err, keyData) => {
          if (err) { return reject({src: 'fs', err: err}); }
    
          jwt.sign('cloud-functions-admin', keyData, jwtOptions, (err, token) => {
            if (err) { return reject({src: 'jwt', err: err}); }
            resolve(token);
          });
        });
      });
    }
    

    示例用法:

    function postToApi(endpoint, body) {
      return getFunctionsAuthToken()
        .then((token) => {
          return rp({
              uri: `https://your-domain.here${endpoint}`,
              method: 'POST',
              headers: {
                Authorization: 'Bearer ' + token
              },
              body: body,
              json: true
            });
        });
    }
    

    如果您在服务器上使用 express,则可以使用 express-jwt 来反序列化令牌。如果配置正确,对于来自 Cloud Functions 的请求,req.user 将是 'cloud-functions-admin'

    const jwt = require('express-jwt');
    
    app.use(jwt({secret: publicKey});
    

    方法二:添加云功能用户

    另一种方法是使用 Firebase 身份验证来避免使用公私钥。这样做的代价是可能会变慢。

    • 优点:无需密钥管理,易于在服务器上验证用户
    • 缺点:Firebase Auth 调用减慢速度 (1-2)
    const admin = require('firebase-admin');
    const rp = require('request-promise-native');
    const firebase = require('firebase');
    const functionsAdminId = 'cloud-functions-admin';
    
    function getFunctionsAuthToken() {
      const fbAuth = firebase.auth();
      if (fbAuth.currentUser && fbAuth.currentUser.uid == uid) {
        // shortcut
        return fbAuth.currentUser.getIdToken(true)
          .catch((err) => {src: 'fb-token', err: err});
      }
    
      return admin.auth().createCustomToken(functionsAdminId)
          .then(function(customToken) {
            return fbAuth.signInWithCustomToken(token)
              .then(() => {
                return fbAuth.currentUser.getIdToken(false)
                  .catch((err) => {src: 'fb-token', err: err});
              })
              .catch((err) => {src: 'fb-login', err: err});
          })
          .catch((err) => {src: 'admin-newtoken', err: err});
    }
    

    示例用法:

    function postToApi(endpoint, body) {
      return getFunctionsAuthToken()
        .then((token) => {
          return rp({
              uri: `https://your-domain.here${endpoint}`,
              method: 'POST',
              headers: {
                Authorization: 'Bearer ' + token
              },
              body: body,
              json: true
            });
        });
    }
    

    在您的服务器上,您将使用以下检查:

    // idToken comes from the received message
    admin.auth().verifyIdToken(idToken)
      .then(function(decodedToken) {
        if (decodedToken.uid != 'cloud-functions-admin') {
          throw 'not authorized';
        }
      }).catch(function(error) {
        // Handle error
      });
    

    或者如果使用 express,您可以将其附加到中间件。

    app.use(function handleFirebaseTokens(req, res, next) {
      if (req.headers.authorization && req.headers.authorization.split(' ')[0] === 'Bearer') {
        var token = req.headers.authorization.split(' ')[1];
        admin.auth().verifyIdToken(idToken)
          .then((decodedToken) => {
            req.user = decodedToken;
            next();
          }, (err) => {
            //ignore bad tokens?
            next();
          });
      } else {
        next();
      }
    });
    
    // later on: req.user.uid === 'cloud-functions-admin'
    


    原始回复:

    如果您的客户端使用来自 SDK 的 Firebase 身份验证来登录,而您的服务器使用 Admin SDK,您可以使用云功能上的客户端 ID 令牌与您的服务器对话,以通过实质上“传递包裹”来验证用户.

    客户端

    firebase.auth().currentUser.getIdToken(/* forceRefresh */ true).then(function(idToken) {
      // Send token to your cloud function
      // ...
    }).catch(function(error) {
      // Handle error
    });
    

    云功能

    // idToken comes from the client app
    admin.auth().verifyIdToken(idToken) // optional (best-practice to 'fail-fast')
      .then(function(decodedToken) {
        // do something before talking to your third-party API
        // e.g. get data from database/secret keys/etc.
        // Send original idToken to your third-party API with new request data
      }).catch(function(error) {
        // Handle error
      });
    

    第三方 API

    // idToken comes from the client app
    admin.auth().verifyIdToken(idToken)
      .then(function(decodedToken) {
        // do something with verified user
      }).catch(function(error) {
        // Handle error
      });
    

    【讨论】:

    • 谢谢,我知道这一点,但我需要的是 Admin SDK 云功能直接调用 3rd 方 API,使用包含“管理员”角色的自定义 JWT 令牌(不是用户的一部分客户 ID 令牌)。如何在 Admin SDK 中生成 JWT 令牌?这几乎就像我需要让它以“管理员”用户身份登录来执行操作。路由请求表单客户端>>Cloud function >> 3rd party API的原因是我可以控制什么是/不发送到3rd party api
    • 作为跟进,如果我最初在客户端 ID 令牌中包含“管理员”角色,但我的应用程序向云功能发送请求,它不会阻止客户端直接调用第 3 部分 API,绕过所有安全。
    • 啊,对。因此,您希望授权您的云函数实例被您的第三方 API 识别为管理员,而不是调用该函数的用户。 Admin SDK 自定义令牌用于允许客户端 Firebase SDK 以导致此混淆的用户身份访问 Firebase 服务。由于执行功能时时间至关重要,我会考虑使用公钥-私钥对,您可以将其存储(受到严格限制)在 Firebase 存储中,也可以使用云功能进行部署。我会拼凑一些东西并编辑上面的答案。
    • 这是一次令人愉快的调查,希望涵盖所有基础。
    • 感谢您的详细回复!出于性能原因,我将尝试使用公钥/私钥,看看是否可以正常工作。再次感谢
    猜你喜欢
    • 1970-01-01
    • 2013-03-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-11-03
    • 2015-08-21
    • 2017-03-01
    • 2019-07-29
    相关资源
    最近更新 更多