Invoking a Firebase callable function from the Firebase Admin SDK
May 15, 2024
May 15, 2024
What you can do however is using the Firebase Admin SDK to create a custom token for the client SDK, and use the client SDK to make the call. đ
What does this looks like?
const admin = require('firebase-admin')
const { initializeApp } = require('firebase/app')
const { getAuth, signInWithCustomToken } = require('firebase/auth')
const { getFunctions, httpsCallable } = require('firebase/auth')
admin.initializeApp({
// Your admin config
})
initializeApp({
// Your client config
})
const token = await admin.auth().createCustomToken('admin')
await signInWithCustomToken(getAuth(), token)
const result = await httpsCallable(getFunctions(), 'myCallableFunction').call({})
Here we created a custom token for a virtual user with UID admin
(it
doesnât need to exist in Firebase Auth). We can verify that in the
function:
const functions = require('firebase-functions')
exports.myCallableFunction = functions.https.onCall(async (data, context) => {
if (!context.auth) {
throw new functions.https.HttpsError(
'unauthenticated',
'User is not authenticated'
)
}
if (context.auth.uid !== 'admin') {
throw new functions.https.HttpsError(
'permission-denied',
'User is not authorized'
)
}
})
With a HTTP function,
we could have made a simple fetch
request to the endpoint.
Why use a callable function then?
In my case, it was because callable functions have auth built-in,
whereas youâre responsible to implement your own auth for HTTP
functions. I found that using a https.onCall
function with a custom
Firebase token was more elegant than configuring some kind of internal
âAPI keyâ.
It turns out itâs also quite easy to invoke a callable function without the Firebase SDK, via a plain HTTP call.
With cURL, calling a function that doesnât have token authentication is as simple as:
curl \
-X POST \
-H 'Content-Type: application/json' \
'https://region-project.cloudfunctions.net/myCallableFunction' \
--data '{"data": {}}'
For the authentication, we need an Authorization: Bearer
header, but
we canât directly use the custom token we generated above. We need to
exchange it for an ID
token first (this happened transparently in the previous example).
We could use the client SDK to do that for us but at that point we might as well use the client SDK to call the function as well. đ
Just for educational purpose, and building off the earlier example, it would look like:
const { getAuth, signInWithCustomToken, getIdToken } = require('firebase/auth')
await signInWithCustomToken(getAuth(), token)
console.log(await getIdToken(getAuth().currentUser))
We could then use that token in the cURL request:
curl \
-X POST \
-H 'Content-Type: application/json' \
-H "Authorization: Bearer $token" \
'https://region-project.cloudfunctions.net/myCallableFunction' \
--data '{"data": {}}'
But if weâre calling the function via fetch
, itâs probably that we
donât want to use the client SDK. Then, exchanging the token would
look like this:
curl \
-X POST \
-H 'Content-Type: application/json' \
"https://identitytoolkit.googleapis.com/v1/accounts:signInWithCustomToken?key=$firebaseApiKey" \
--data "{\"token\": \"$customToken\", \"returnSecureToken\": true}"
This returns an idToken
that we can use as Authorization: Bearer
in
the invocation of the callable function as seen above.
Note: if youâre wondering about returnSecureToken
, itâs
documented
as âshould always be trueâ.
Without it, the endpoint returns only an idToken
with no expiresIn
nor refreshToken
, so my guess is that itâs a token that⌠doesnât
expire? Which is considered insecure.