App check unwanted enforcement on Firebase callable functions - firebase

Without having changed anything in my Firebase callable functions code, but having re-deployed them, now they suddenly start rejecting all function invocations from my app with the error shown below. I would like NOT to use App Check until I am ready to make the changes needed. How do I stop my callable (https.onCall) Firebase functions from rejecting invalid App Checks, and instead only reject invalid Authentication?
Failed to validate AppCheck token. FirebaseAppCheckError: Decoding App Check token failed. Make sure you passed the entire string JWT which represents the Firebase App Check token.
at FirebaseAppCheckError.FirebaseError [as constructor] (/workspace/node_modules/firebase-admin/lib/utils/error.js:44:28)
at FirebaseAppCheckError.PrefixedFirebaseError [as constructor] (/workspace/node_modules/firebase-admin/lib/utils/error.js:90:28)
at new FirebaseAppCheckError (/workspace/node_modules/firebase-admin/lib/app-check/app-check-api-client-internal.js:187:28)
at /workspace/node_modules/firebase-admin/lib/app-check/token-verifier.js:82:19
at processTicksAndRejections (internal/process/task_queues.js:97:5) {
errorInfo: {
code: 'app-check/invalid-argument',
message: 'Decoding App Check token failed. Make sure you passed the entire string JWT which represents the Firebase App Check token.'
},
codePrefix: 'app-check'
}
Callable request verification failed: AppCheck token was rejected. {"verifications":{"app":"INVALID","auth":"VALID"}}
The code rejecting all requests due to invalid App Check is super simple:
const functions = require("firebase-functions");
const admin = require("firebase-admin");
admin.initializeApp();
exports.example = functions.https.onCall((data, context) => {
return "test";
}
Package.json:
"engines": {
"node": "12"
},
"main": "index.js",
"dependencies": {
"firebase-admin": "^9.10.0",
"firebase-functions": "^3.14.1"
},

I had the same experience. The docs say that you are supposed to check like this[1]:
if (context.app == undefined) {
throw new functions.https.HttpsError(
'failed-precondition',
'The function must be called from an App Check verified app.')
}
But, this is not the case in my experience, the App Check starts to be enforced immediately the moment you add App Check to your app.
EDIT:
moreover, even without doing any check in my code, I can see this in the logs whenever I call one of my functions:
Callable request verification passed {"verifications":{"auth":"VALID","app":"VALID"}}
So it seems App Check happens automatically, at least in Callable Functions. If you want to bypass AppCheck in one of your functions, you might want to try an HTTP function instead (not Callable).
[1] Source https://firebase.google.com/docs/app-check/cloud-functions

From protocol specification for https.onCall:
Optional [Header]: X-Firebase-AppCheck: The Firebase App Check token provided by the client app making the request. The backend automatically verifies this token and decodes it, injecting the appId in the handler's context. If the token cannot be verified, the request is rejected. (Available for SDK >=3.14.0)
My guess is that calls to your callable function contains an invalid App Check token.
If you haven't configured DeviceCheck or/and App Attest attestation providers on your project but have included the App Check library on your client, your client code may be including a dummy App Check token when calling your function (full details on this github issue).
Firebase team is working through changes to make the experience less confusing. Please follow along in http://github.com/firebase/firebase-functions/issues/967 and https://github.com/FirebaseExtended/flutterfire/issues/6794 for status.

My problem is the same when I run the app on an emulator, after running on a real device is running without any problems. That's what happens to me.

Related

How to connect Firebase Admin to Emulator Auth

I am struggling to connect to the emulated Firebase Auth service via the Firebase Admin SDK. I cut down the code to really make the problem stand out, and hope someone can help.
This is the code of the test.js I run (in NodeJS):
// Someone said these two lines should allow the firebase-admin
// SDK to connect to the emulators, but... no.
process.env['GCLOUD_PROJECT'] = 'my-firebase-project-id'
process.env['FIRESTORE_EMULATOR_HOST'] = 'localhost:8080'
const admin = require('firebase-admin')
const app = admin.initializeApp()
const auth = app.auth()
console.log('I have an auth service object')
auth.listUsers().then(users => console.log(users))
I run the emulators like this:
firebase emulators:start --only auth
When I run the test.js file, I get this:
PS C:\...\functions> node .\test.js
I have an auth service object
(node:18232) UnhandledPromiseRejectionWarning: Error: Credential implementation provided to initializeApp() via the "credential" property failed to fetch a valid Google OAuth2 access token with the following error: "Error fetching access token: Error while making request: getaddrinfo EAI_AGAIN metadata.google.internal. Error code: EAI_AGAIN".
at FirebaseAppError.FirebaseError [as constructor] (C:\...\functions\node_modules\firebase-admin\lib\utils\error.js:44:28)
at FirebaseAppError.PrefixedFirebaseError [as constructor] (C:\...\functions\node_modules\firebase-admin\lib\utils\error.js:90:28)
at new FirebaseAppError (C:\...\functions\node_modules\firebase-admin\lib\utils\error.js:125:28)
at C:\...\functions\node_modules\firebase-admin\lib\app\firebase-app.js:87:19
at processTicksAndRejections (internal/process/task_queues.js:97:5)
I run this on Windows with the following versions of firebase:
"firebase-admin": "^10.0.2",
"firebase-functions": "^3.18.1",
I read about getting a secret credentials key and adding its path like this:
process.env['GOOGLE_APPLICATION_CREDENTIALS'] = 'C:\\...\\functions\\.runtimekey.json'
And that 'works' in as much as I then can access the real cloud auth instance (as long as the emulators is off) but that isn't what I want. I want to connect firebase-admin and get a list of users in the emulated Auth instance.
Many thanks for any help you can offer!
Set the environment variable FIREBASE_AUTH_EMULATOR_HOST
export FIREBASE_AUTH_EMULATOR_HOST=localhost:9099
Do not include the protocol scheme (i.e http/https)
Or in your case:
process.env['FIREBASE_AUTH_EMULATOR_HOST'] = 'localhost:9099'
Then you can initialise the app as per normal
admin.initializeApp()
Worked for me (after I finally figured out not to include the protocol scheme)
source: https://firebase.google.com/docs/emulator-suite/connect_auth#admin_sdks

Custom logging from firebase function

I'm trying to follow this guide to put some custom logging into a firebase function. The function itself is running, and I can see the data being passed in (it's an https 'callable' function). But as soon as it hits the line where it tries to actually write that log entry, I get "Error: 7 PERMISSION_DENIED"
Since the console.log() calls write to the cloud logs, I'd assumed the firebase function has access to Cloud Logging. But perhaps it needs additional permission? I can't find any reference to where this should be set on that page though.
// Logging, logName, region, functions are provided by the surrounding app
const logging = new Logging()
const log = logging.log(logName)
const METADATA = {
resource: {
type: 'cloud_function',
labels: {
function_name: 'CustomLog',
region
}
}
};
exports = module.exports = functions.https.onCall(async data => {
const exVersion = 6
const exId = data.exId
console.log('***** exVersion:', exVersion, 'exId:', exId) // exId from caller
const entry = log.entry(METADATA, data.error) // data.error from caller
console.log('METADATA:', METADATA) // Shows in Logs Explorer
console.log('entry:', entry) // Shows in Logs Explorer
log.write(entry) // Results in Error: 7 PERMISSION_DENIED
return {
exVersion,
exId,
}
})
If I run it from the CLI using firebase function:shell, the log entry is created correctly, so I'm pretty confident the code is correct.
OK, I finally tracked it down. According to this answer, the service account used by firebase functions is {project-id}#appspot.gserviceaccount.com, and in my project, that account did not have the 'Logs Writer' role. Adding that role solves the problem.
I find it odd that the firebase functions don't need that role to log messages using console.log(), but perhaps that call is intercepted by the functions environment, and the logs are written as a different service account. It also explains why the functions running locally were able to write the logs, as they run using the 'owner' service account, which has full access.
According to the Firebase documentation page you have linked:
The recommended solution for logging from a function is to use the
logger SDK. You can instead use standard JavaScript logging calls such
as console.log and console.error, but you first need to require a
special module to patch the standard methods to work correctly:
require("firebase-functions/lib/logger/compat");
Once you have required the logger compatibility module, you can use console.log() methods as normal in your code.
Thus you might to require this library, however I am not sure this is producing your "Error: 7 PERMISSION_DENIED error, but you might also try some solutions that have worked for some members of the community.
Perhaps the logging API is not enabled in your project. You'll get a permission denied error when attempting to use it in that case.
It's a couple levels in, but the guide you linked points to
https://github.com/googleapis/nodejs-logging#before-you-begin, which includes a step to "Enable the Cloud Logging API"

Firebase App Check + Cloud functions : Failed to validate AppCheck token

I have recently added Firebase App Check in my Flutter app.
I am calling Cloud functions with the https.onCall() method. However i am receiving this error :
> {"severity":"WARNING","message":"Failed to validate AppCheck token. FirebaseAppCheckError: Decoding App Check token failed. Make sure you passed the entire string JWT which represents the Firebase App Check token.
at FirebaseAppCheckError.FirebaseError [as constructor] (/Users/foxtom/Desktop/Cloud Functions/functions/node_modules/firebase-admin/lib/utils/error.js:44:28)
at FirebaseAppCheckError.PrefixedFirebaseError [as constructor] (/Users/foxtom/Desktop/Cloud Functions/functions/node_modules/firebase-admin/lib/utils/error.js:90:28)
at new FirebaseAppCheckError (/Users/foxtom/Desktop/Cloud Functions/functions/node_modules/firebase-admin/lib/app-check/app-check-api-client-internal.js:187:28)
at /Users/foxtom/Desktop/Cloud Functions/functions/node_modules/firebase-admin/lib/app-check/token-verifier.js:82:19
at processTicksAndRejections (node:internal/process/task_queues:96:5) {
errorInfo: {
code: 'app-check/invalid-argument',
message: 'Decoding App Check token failed. Make sure you passed the entire string JWT which represents the Firebase App Check token.'
},
codePrefix: 'app-check'
}"}
> {"verifications":{"app":"INVALID","auth":"MISSING"},"logging.googleapis.com/labels":{"firebase-log-type":"callable-request-verification"},"severity":"WARNING","message":"Callable request verification failed: AppCheck token was rejected."}
The severity seems to be WARNING only but it doesn't execute the function. My function only contains a console.log()
In my app i have this error :
W/FirebaseContextProvider( 6788): Error getting App Check token; using placeholder token instead. Error: com.google.firebase.FirebaseException: Error returned from API. code: 403 body: App attestation failed.
I/flutter ( 6788): Error is : [firebase_functions/unauthenticated] Unauthenticated
I have not enforced anything like suggested in the documentation
This is preventing me from using Cloud Functions and i can disable App Check for my app anymore...
EDIT :
I add that Firebase Storage and RealTime Database are working fine without any debug AppCheck token when it's not enforced.
What can I do ?
I created a reproducible code sample, which you see here: https://github.com/nilsreichardt/playground/tree/firebase-app-check-cloud-function-unauthenticated-issue/firebase-app-check-cloud-functions-unauthentificated
Therefore, I created a detailed issue in the FlutterFire repository: https://github.com/FirebaseExtended/flutterfire/issues/6794
A first workaround already posted as a comment and I'm sure that more workarounds or solutions will follow.
Firebaser here.
Thank you for reporting this issue -- we have now released a fix to all platforms that should resolve this issue. Please refer to the Github issue for full details.

Why doesn't Firebase admin auth in node.js use ADC?

Does anyone know why Firebase admin auth in node.js doesn't use ADC (Application Default Credentials)? I always have to set GOOGLE_APPLICATION_CREDENTIALS to a credentials file to get auth to work. Everything else (firestore, compute, storage etc.) works fine with ADC.
For instance, this code works only when GOOGLE_APPLICATION_CREDENTIALS is set to a valid credentials file, even though I'm logged into my Firebase project and my gcloud project:
import * as admin from 'firebase-admin'
admin.initializeApp()
async function listAllUsers(users: any[], matchRegex: RegExp, nextPageToken?: string) {
// List batch of users, 1000 at a time.
const listUsersResult = await admin.auth().listUsers(1000, nextPageToken)
.catch(function (error) {
console.log('Error listing users:', error);
});
if (listUsersResult) {
listUsersResult.users.forEach(function (userRecord) {
if (matchRegex.test(userRecord.email || '<none>') ||
matchRegex.test(userRecord.displayName || '<none>') ||
matchRegex.test(userRecord.uid))
users.push(userRecord.toJSON())
});
if (listUsersResult.pageToken) {
// List next batch of users.
console.log(`next batch...`)
listAllUsers(users, matchRegex, listUsersResult.pageToken);
}
}
}
If that env var is not set, I get this error:
Error listing users: FirebaseAuthError: Failed to determine project ID for Auth.
Initialize the SDK with service account credentials or set project ID as an app option.
Alternatively set the GOOGLE_CLOUD_PROJECT environment variable.
But setting GOOGLE_CLOUD_PROJECT is not enough either. When I do that, I get:
Error listing users: FirebaseAuthError: //cloud.google.com/docs/authentication/. Raw server response: "{"error":{"code":403,"message":"Your application has authenticated using end user credentials from the Google Cloud SDK or Google Cloud Shell which are not supported by the identitytoolkit.googleapis.com. We recommend configuring the billing/quota_project setting in gcloud or using a service account through the auth/impersonate_service_account setting. For more information about service accounts and how to use them in your application, see https://cloud.google.com/docs/authentication/.","errors":[{"message":"Your application has authenticated using end user credentials from the Google Cloud SDK or Google Cloud Shell which are not supported by the identitytoolkit.googleapis.com. We recommend configuring the billing/quota_project setting in gcloud or using a service account through the auth/impersonate_service_account setting. For more information about service accounts and how to use them in your application, see https://cloud.google.com/docs/authentication/.","domain":"usageLimits","reason":"accessNotConfigured","extendedHelp":"https://console.developers.google.com"}],"status":"PERMISSION_DENIED"}}"
at FirebaseAuthError.FirebaseError [as constructor] (/c/dss/Product/Horizon/horizon/packages/renderer/node_modules/firebase-admin/lib/utils/error.js:43:28)
at FirebaseAuthError.PrefixedFirebaseError [as constructor] (/c/dss/Product/Horizon/horizon/packages/renderer/node_modules/firebase-admin/lib/utils/error.js:89:28)
at new FirebaseAuthError (/c/dss/Product/Horizon/horizon/packages/renderer/node_modules/firebase-admin/lib/utils/error.js:148:16)
at Function.FirebaseAuthError.fromServerError (/c/dss/Product/Horizon/horizon/packages/renderer/node_modules/firebase-admin/lib/utils/error.js:187:16)
at /c/dss/Product/Horizon/horizon/packages/renderer/node_modules/firebase-admin/lib/auth/auth-api-request.js:1490:49
As I said though, all other Firebase admin features seem to work fine with ADC; they automatically pick up the current project and my logged in account.

can't use cypress to test app using using firestore local emulator

I have a app built with vue and firebase/firestore. I use the firebase emulator to local development and am trying to integrate my dev workflow with cypress. But i get a error in cypress that do not occur if i access the app from browser.
Firebase CLI version is 7.9.0 and Cypress version is "^3.8.0"
My npm scripts to load everything are below:
"start": "firebase emulators:exec --only firestore \"npm run dev:appandtest\"",
"dev:appandtest": "concurrently -n \"app,test\" -c \"bgYellow.black,bgWhite.black\" \"npm:dev:app\" \"npm:dev:test\"",
"dev:app": "webpack-dev-server --config build/webpack.dev.js",
"dev:test": "npx cypress open",
The local server runs on port 9000 and the firebase emulator on port 8080.
After things are running, if i access the app from a normal browser everything is fine as this screen shows.
normal
Then i tried to run a basic cypress test with this code
describe('The Home Page', function () {
it('successfully loads', function () {
cy.visit('/');
});
});
And i got the errors messages below:
[2019-12-14T15:29:24.725Z] #firebase/firestore: Firestore (6.6.2): Could not reach Cloud Firestore backend. Backend didn't respond within 10 seconds.
This typically indicates that your device does not have a healthy Internet connection at the moment. The client will operate in offline mode until it is able to successfully connect to the backend.
error.ts:166 Uncaught (in promise) FirebaseError: Failed to get document because the client is offline.
at new FirestoreError (http://localhost:9000/bundle.js:11739:149)
at Object.next (http://localhost:9000/bundle.js:16734:8)
at next (http://localhost:9000/bundle.js:16725:4704)
at http://localhost:9000/bundle.js:16430:411
I took a screenshot also:
buggy
I tried to research answers but wasn't able to find one. Thanks in advance for any help.
The solution to this problem, at least for now, is to enable experimentalForceLongPolling, like this:
// NOTE: do NOT put this in production.
firebase.firestore().settings({ experimentalForceLongPolling: true })
Important: this is an experimental feature and you should put it in some conditional checks with environment variables. You should not use this in production environment.
The reason for this is best described here:
The default behavior of Firestore's web SDK is to make use of WebChannel's streaming mode. The client makes what looks like an XHR, but then the server will hold the response open for 60 seconds and send as many server-initiated responses as it can during that time window.
The experimentalForLongPolling option forces the server to send only a single response per request.
And here:
That is the same workaround we are using in cypress. I think the underlying problem is that Cypress is intercepting all network traffic so it can monitor and sometimes mock. However, the webchannel protocol used by firestore has multiple replies over the same http request. The Cypress code cannot handle this and will only forward the first reply and ignore the rest.
In the v9 new API you can't rely on
provideFirebaseApp(() => initializeApp(environment.firebase)),
where environment.firebase includes { experimentalAutoDetectLongPolling: true }.
Instead you have to explicitly do this in the provideFirestore method.
provideFirestore(() => {
let firestore;
if (environment.useEmulators) {
// bug: experimentalAutoDetectLongPolling not picked up via `getFirestore`
const app = initializeApp(environment.firebase)
firestore = initializeFirestore(app, {
experimentalAutoDetectLongPolling: true
})
connectFirestoreEmulator(firestore, 'localhost', 8080)
} else {
firestore = getFirestore();
}
return firestore;
}),

Resources