Search code examples
pythonfirebasefirebase-admin

Initializing Firebase Admin via Environment Variables without storing serviceAccount.json


I am trying to initialize firebase-admin on my Flask API. Based off the documentation, the method initialize_app can take a dictionary as an input as seen here:

https://github.com/firebase/firebase-admin-python/blob/6d826fd15c87db62f7501f569b6e0a762d17a05e/firebase_admin/credentials.py#L85

That said, I structured my code as follows:

import firebase_admin
from firebase_admin import auth, credentials

...

firebase_admin.initialize_app({ \
    credentials.Certificate({ \
        "type": "service_account", \
        "project_id": os.environ.get('FIREBASE_PROJECT_ID'), \
        "private_key_id": os.environ.get('PRIVATE_KEY_ID'), \
        "private_key": os.environ.get('FIREBASE_PRIVATE_KEY').replace('\\n', '\n'), \
        "client_email": os.environ.get('FIREBASE_CLIENT_EMAIL'), \
        "client_id": os.environ.get('CLIENT_ID'), \
        "auth_uri": os.environ.get('AUTH_URI'), \
        "token_uri": os.environ.get('TOKEN_URI'), \
        "auth_provider_x509_cert_url": os.environ.get('AUTH_PROVIDER_X509_CERT_URL'), \
        "client_x509_cert_url": os.environ.get('CLIENT_X509_CERT_URL'), \
    }), 
})

Now, I'm getting this error:

ValueError: Illegal Firebase credential provided. App must be initialized with a valid credential instance.

I would ideally want to set up the application like this as I would prefer not to store the serviceAccount.json on the cloud. Many of the examples I find are simply doing what I don't want to do.

What am I missing here?

Edit:

I am using the standard export <property_name>="..." on my Mac OS terminal which I presume is the same as Linux environment. As a result, os.environ.get(<property_name>) gets the corresponding value.

For reference:

I am trying to do the same thing as this:

https://www.benmvp.com/blog/initializing-firebase-admin-node-sdk-env-vars/

But with Python

Edit:

Looking at the source code here:

https://github.com/firebase/firebase-admin-python/blob/6d826fd15c87db62f7501f569b6e0a762d17a05e/firebase_admin/__init__.py#L209

It appears that the exception is being thrown here. However, in my Flask API, I have the following:

cert = { \
        "type": "service_account", \
        "project_id": os.environ.get('FIREBASE_PROJECT_ID'), \
        "private_key_id": os.environ.get('PRIVATE_KEY_ID'), \
        "private_key": os.environ.get('FIREBASE_PRIVATE_KEY').replace('\\n', '\n'), \
        "client_email": os.environ.get('FIREBASE_CLIENT_EMAIL'), \
        "client_id": os.environ.get('CLIENT_ID'), \
        "auth_uri": os.environ.get('AUTH_URI'), \
        "token_uri": os.environ.get('TOKEN_URI'), \
        "auth_provider_x509_cert_url": os.environ.get('AUTH_PROVIDER_X509_CERT_URL'), \
        "client_x509_cert_url": os.environ.get('CLIENT_X509_CERT_URL'), \
    

print(type(credentials.Certificate(cert)), isinstance(credentials.Certificate(cert), credentials.Certificate), isinstance(credentials.Certificate(cert), credentials.Base))

To which the output is:

<class 'firebase_admin.credentials.Certificate'> True True

This doesn't make sense.. Since the following block:

if not isinstance(credential, credentials.Base):

Runs if isinstance(credential, credentials.Base) is false. But I have the values as true.


Solution

  • Thanks to, smac89, for pointing me in the right direction.

    The change is very minor to make it work:

    firebase_admin.initialize_app({ \
        credential=credentials.Certificate({ \  # <------- This change
            "type": "service_account", \
            "project_id": os.environ.get('FIREBASE_PROJECT_ID'), \
            "private_key_id": os.environ.get('PRIVATE_KEY_ID'), \
            "private_key": os.environ.get('FIREBASE_PRIVATE_KEY').replace('\\n', '\n'), \
            "client_email": os.environ.get('FIREBASE_CLIENT_EMAIL'), \
            "client_id": os.environ.get('CLIENT_ID'), \
            "auth_uri": os.environ.get('AUTH_URI'), \
            "token_uri": os.environ.get('TOKEN_URI'), \
            "auth_provider_x509_cert_url": os.environ.get('AUTH_PROVIDER_X509_CERT_URL'), \
            "client_x509_cert_url": os.environ.get('CLIENT_X509_CERT_URL'), \
        }), 
    })
    

    The method signature for initialize_app is:

    def initialize_app(credential=None, options=None, name=_DEFAULT_APP_NAME):
    
    

    By not specifying the input to be credential, the input was not read correctly. Guess I learnt something new about Python default values. :P