admin.auth().verifyIdToken(tokenId)
.then((decoded) => res.status(200).send(decoded))
I understand verifyIdToken()
can verify a user id token from Firebase Authentication clients. However we need to protect our Cloud Function by making sure database queries are limited by the security rules defined for the database for the user identified in the token. Given that the admin SDK has unlimited access by default, how to I limit its access to just that of the authenticated user?
Take a look at the following HTTPS function. It performs the following tasks:
userApp
to make a database query to some protect path.userApp.delete()
is called in all circumstances. Don't forget to do this, or you will leak memory as more users access this function.
Here's a working function:
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)
})
})
Again, note that userApp.delete()
should be called in all circumstances to avoid leaking instances of App. If you had the idea to instead give each new App a unique name based on the user, that's not a good idea, because you can still run out of memory as new users keep accessing this function. Clean it up with each call to be safe.
Also note that userApp.delete()
should be called before the response is sent, because sending a response terminates the function, and you don't want to have the cleanup interrupted for any reason.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With