Error: Could not load the default credentials (Firebase function to firestore)

29,655

Solution 1

I had the same error "Could not load the default credentials".

The error occured after updating my project dependencies with npm update. More precisely firebase-admin and firebase-functions.

Before update:

"dependencies": {
    "@google-cloud/firestore": "^1.3.0",
    "firebase-admin": "~7.0.0",
    "firebase-functions": "^2.2.0"
}

After update:

"dependencies": {
    "@google-cloud/firestore": "^1.3.0",
    "firebase-admin": "^8.6.0",
    "firebase-functions": "^3.3.0"
}

I added the serviceAccountKey.json to my project and changed the imports with the code provided at the service account setting of my firebase project.

From :

var admin = require('firebase-admin')

admin.initializeApp()

To:

var admin = require('firebase-admin');    
var serviceAccount = require('path/to/serviceAccountKey.json');

admin.initializeApp({
  credential: admin.credential.cert(serviceAccount),
  databaseURL: 'https://my-project.firebaseio.com'
});

See @Fernando Rocha's answer below to access the account setting of your firebase project.

Solution 2

What I first did to solve it was add my firebase admin sdk key to my project.

I downloaded it at

https://console.firebase.google.com/u/0/project/**YOUR_PROJECT_ID**/settings/serviceaccounts/adminsdk

Admin SDK Key Download Page

then at admin.initializeApp(); I changed to:

admin.initializeApp({
    credential: admin.credential.cert(require('../keys/admin.json'))
});

My folder structure is

├── key
│   ├── admin.json
├── src
│   ├── index.ts

HOWEVER, a better practice and safer approach, as some mentioned already: You could use environment variables to store your credentials, this way you won't commit it to a repository such as Github, keep it safer from safety breaches and won´t make it hardcoded.

Depending on your project and where you'll deploy it there's a different way to do it.

There are many tutorials around on how to create and access env variables (like this one), but you could use a name it like the example below:

GOOGLE_APPLICATION_CREDENTIALS="/home/admin.json"

Solution 3

@aldobaie's answer helped me figure out what was going on for my use case. For those who are not looking to add async/await to all their calls, remember that the firestore calls return promises, so prepending them with return has the same effect.

In my case:

function doSomething(...) {
    return admin.firestore().collection(...).doc(...).get()
        .then((doc) => {...})
        .catch(err => {...})
}

module.exports = functions.firestore.document('collection/{docId}').onWrite((change, context) => {
    return doSomething()
})

I think the accepted answer goes against Firebase's recommend configuration. The function environment has access to admin credentials already, and passing your key in the code is not recommended.

I do it like this:

const functions = require('firebase-functions')
const admin = require('firebase-admin')
admin.initializeApp(functions.config().firebase)

Solution 4

I ran into the same problem myself. Sometimes the function works and many times it would through the Error: Could not load the default credentials error. The problem I believe have been solved by watching for the Callbacks. You have to keep the function running until the callbacks have been called using the await and async prefixes.

Firebase Cloud Functions don't allow the access to the processor through callbacks once it's been terminated! That's why we get the Error: Could not load the default credentials error.

So, whenever you have a .then() function prefix it with await and prefix the function it's inside it with async and prefix any call to the function with await.

async function registerUser(..) {
    ...
    await admin.firestore().collection(..)...
    ...
}

I hope this helps you out!

Solution 5

Another option is to set the service account key in an environmental variable instead of setting it with a call to firebaseAdmin.initializeApp({ credential }).

Linux

export GOOGLE_APPLICATION_CREDENTIALS="[PATH]"
export GOOGLE_APPLICATION_CREDENTIALS="/home/user/Downloads/[FILE_NAME].json"

Windows PowerShell

$env:GOOGLE_APPLICATION_CREDENTIALS="[PATH]"
$env:GOOGLE_APPLICATION_CREDENTIALS="C:\Users\username\Downloads\[FILE_NAME].json"

Postscript: An even better option might be to use the local emulator suite.

Share:
29,655
BurnDownTheIgloo
Author by

BurnDownTheIgloo

Updated on July 08, 2022

Comments

  • BurnDownTheIgloo
    BurnDownTheIgloo almost 2 years

    I am attempting to write an onCall function for Firebase Cloud Functions that performs advanced querying tasks on a firestore database (i.e. checking a text query up against AutoML natural lang to get a category, etc) but I keep running into a problem trying to query the database from the function:

    Error getting documents ::  Error: Could not load the default credentials. Browse to https://cloud.google.com/docs/authentication/getting-started for more information.
        at GoogleAuth.getApplicationDefaultAsync (/srv/node_modules/google-auth-library/build/src/auth/googleauth.js:161:19)
        at <anonymous>
        at process._tickDomainCallback (internal/process/next_tick.js:229:7)
    

    Function:

    const functions = require('firebase-functions');
    const admin = require('firebase-admin');
    admin.initializeApp();
    const db = admin.firestore();
    
    exports.query = functions.https.onCall((data, context) => {
        const text = data.text;
        var results = [];
        const promise = db.collection('providers').get()
        promise.then((snapshot) => {
            console.log('marker');
            snapshot.forEach((doc) => {
                results.push({id: doc.id, data: doc.data()});
            });
            console.log('yessir');
            return {results: results};
        }).catch((err) => {
            console.log('Error getting documents :: ', err)
            console.log('nosir');
            return {results: "no results"};
        });
    });
    

    Longer output:

    Function execution started
    Function execution took 8ms, finished with status code: 200
    Error getting documents :: (etc, same error)
    nosir
    

    Example 2 (no change in running):

    Function execution started
    Function execution took 1200 ms, finished with status code: 200
    marker
    yessir
    

    I can't figure out where this problem is coming from or how to resolve it. Any help?

    Regards.

  • Fernando Rocha
    Fernando Rocha over 4 years
    @BurnDownTheIgloo did you try this?
  • Sergio
    Sergio over 4 years
    how do you set the Environment variable when deploying to Google Cloud Functions for Firebase?
  • Sergio
    Sergio over 4 years
    How do you do it in a Typescript project? How do I instruct tsc to put the cert in the transpiled folder tree?
  • Sergio
    Sergio over 4 years
    How do you do it in a Typescript project? How do I instruct tsc to put the cert in the transpiled folder tree?
  • Shaun Luttin
    Shaun Luttin over 4 years
    As far as I recall, we do not need those credentials when we have deployed to Firebase @Sergio.
  • Adrian Marinica
    Adrian Marinica over 4 years
    This worked nicely. For others stumbling onto this issue, there might more than one promise in the function code. Each one needs to be awaited, not only the main one.
  • its_tayo
    its_tayo over 4 years
    @Sergio Hi, I used this quick hack to get it working with typescript 1. import * as serviceAccount from 'path/to/.json' 2. admin.initializeApp({ credential: admin.credential.cert(serviceAccount as any) }) 3. ``` "resolveJsonModule": true``` in tsconfig.json
  • Snorre
    Snorre over 4 years
    In my case the default credentials worked on some machines, and others not, but adding the service account was of course a more stable solution. Great answer and very thorough explanation.
  • lijo
    lijo over 4 years
    This answer pointed me in the right direction. I got the error because I used a different user account to deploy. Redeployed with the default account, and it worked. No need for explicit credential config.
  • shankie_san
    shankie_san over 4 years
    This sorted it for me – returning a promise solved my authentication problem!
  • Karl Hofmann
    Karl Hofmann over 4 years
    @Sergio I just placed it in the lib folder manually and it worked.
  • aragalie
    aragalie over 4 years
    had the same issue, and initially I assumed that my problem is related to this ticket: github.com/googleapis/google-auth-library-nodejs/issues/798 but the issue was actually with the promises not awaited properly, as mentioned in this answer.
  • Tim Philip
    Tim Philip over 4 years
    This was my exact same issue as well. One cloud function started failing when I converted it to async/await, and I had forgot to return the resulting promise. You can see in the firebase logs that the function returned successfully, and then several seconds later would log the authentication issue. The async code was trying to execute after the cloud function had terminated, and the auth info was now gone. Makes total sense.
  • Rafael Rocha
    Rafael Rocha over 4 years
    @Sergio you can see this great tutorial for more details: youtube.com/watch?v=7IkUgCLr5oA
  • Tetsujin no Oni
    Tetsujin no Oni over 4 years
    This answer is extremely bad practice because it involves putting service account keys into the cloud function source. While it will make the keys available to the project, it is inappropriate to put into practice.
  • Tetsujin no Oni
    Tetsujin no Oni over 4 years
    This is not an appropriate answer, as it puts a revocable service account credential embedded in the function. Secrets in your source repository is not appropriate, and secrets deployed where a service account credential rotation forces redeployment of your cloud function is also not appropriate.
  • Fernando Rocha
    Fernando Rocha over 4 years
    @TetsujinnoOni You should then propose an appropriate answer
  • Tetsujin no Oni
    Tetsujin no Oni over 4 years
    All of the answers which I upvoted and do not do this terrible security practice are appropriate, it would be inappropriate for me to add an answer.
  • Fernando Rocha
    Fernando Rocha about 4 years
    This is an repeated answer
  • C.Gadd
    C.Gadd about 4 years
    This is a tricky problem and I appreciate your answer, most people just want things to work and this apparently got things working for many. However, while this might or might not work for some people, (it did not for me) it could create security issues and is not an optimal solution. There must be a better way. If I find a better solution I will post it.
  • Fernando Rocha
    Fernando Rocha about 4 years
    @C.Gadd you could also try using it as an environment variable
  • C.Gadd
    C.Gadd about 4 years
    Correct. When you deploy to Firebase your functions calls will be from the cloud and thus you are already authenticated. This is because you are not calling the functions locally (as you would if you used firebase serve over * firebase deploy*) where you are not logged in and authenticated but from the cloud where you are logged in and authenticated. Thus no authentication errors!