如何使用经过身份验证的id令牌和数据库规则来保护firebase Cloud Function HTTP端点?

时间:2018-02-02 04:31:12

标签: javascript firebase firebase-realtime-database firebase-authentication google-cloud-functions

admin.auth().verifyIdToken(tokenId)
      .then((decoded) => res.status(200).send(decoded))

我了解verifyIdToken()可以验证来自Firebase身份验证客户端的用户ID令牌。但是,我们需要通过确保数据库查询受到为令牌中标识的用户定义的数据库安全规则的限制来保护我们的云功能。鉴于admin SDK默认具有无限制访问权限,如何限制其对经过身份验证的用户的访问?

1 个答案:

答案 0 :(得分:8)

查看以下HTTPS功能。它执行以下任务:

  1. 使用Admin SDK验证Firebase身份验证ID令牌。令牌来自查询字符串(但您应use a better solution传输令牌)。
  2. 从解码后的令牌中提取用户的UID。
  3. 制作默认Firebase初始化配置对象的副本,然后使用UID将名为databaseAuthVariableOverride的属性添加到limit the privileges of the caller
  4. 使用新选项初始化App对象的新非默认实例(名为" user")。此App object现在可用于访问数据库,同时遵守该用户的安全规则。
  5. Admin SDK与userApp一起用于对某些保护路径进行数据库查询。
  6. 如果查询成功,请记住发送给cleint的响应。
  7. 如果查询因安全级别而失败,请记住发送给客户端的错误响应。
  8. 清理Admin SDK的这个实例。此代码采取所有预防措施,以确保在所有情况下都调用userApp.delete()请勿忘记这样做,否则当更多用户访问此功能时,您会泄漏内存。
  9. 实际发送回复。这将终止该功能。
  10. 这是一个工作职能:

    admin.initializeApp(functions.config().firebase)
    
    exports.authorizedFetch = functions.https.onRequest((req, res) => {
        let userApp
        let response
        let isError = false
    
        const token = req.query['token']
    
        admin.auth().verifyIdToken(token)
        .then(decoded => {
            // Initialize a new instance of App using the Admin SDK, with limited access by the UID
            const uid = decoded.uid
            const options = Object.assign({}, functions.config().firebase)
            options.databaseAuthVariableOverride = { uid }
            userApp = admin.initializeApp(options, 'user')
            // Query the database with the new userApp configuration
            return admin.database(userApp).ref("/some/protected/path").once('value')
        })
        .then(snapshot => {
            // Database fetch was successful, return the user data
            response = snapshot.val()
            return null
        })
        .catch(error => {
            // Database fetch failed due to security rules, return an error
            console.error('error', error)
            isError = true
            response = error
            return null
        })
        .then(() => {
            // This is important to clean up, returns a promise
            if (userApp) {
                return userApp.delete()
            }
            else {
                return null
            }
        })
        .then(() => {
            // send the final response
            if (isError) {
                res.status(500)
            }
            res.send(response)
        })
        .catch(error => {
            console.error('final error', error)
        })
    })
    

    再次注意,应该在所有情况下调用userApp.delete()以避免泄漏App的实例。如果您有想法为每个新应用程序提供基于用户的唯一名称,那不是一个好主意,因为当新用户继续访问此功能时,您仍然可能会耗尽内存。每次通话都要清理干净。

    另请注意,{<1}}应在发送响应之前调用,因为发送响应会终止该功能,并且您不希望任何中断清除原因。