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

JLI*_*LIN 2 javascript firebase firebase-authentication firebase-realtime-database google-cloud-functions

admin.auth().verifyIdToken(tokenId)
      .then((decoded) => res.status(200).send(decoded))
Run Code Online (Sandbox Code Playgroud)

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

Dou*_*son 11

看看下面的HTTPS函数.它执行以下任务:

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

这是一个工作功能:

const admin = require("firebase-admin")
admin.initializeApp()

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)
    })
})
Run Code Online (Sandbox Code Playgroud)

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

另请注意,userApp.delete()应在发送响应之前调用,因为发送响应会终止该函数,并且您不希望因任何原因中断清理.