Initializing Firebase Admin via Environment Variables without storing serviceAccount.json

Question:

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.

Asked By: jeff

||

Answers:

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. 😛

Answered By: jeff

This answer was quite useful for me with a related problem. I was ok setting the key content as a secret environment variable. However, I wasn’t sure how to set it without constructing the dictionary. In the end, I could just json.loads() the content of the secret environment key, and it worked. I thought it was worth sharing this:

import json, os
import firebase_admin
from firebase_admin.credentials import Certificate

key_dict = json.loads(
    os.environ["FIREBASE_SERVICE_ACCOUNT_CREDENTIALS_FROM_ENV"]
)

firebase_admin.initialize_app(Certificate(key_dict))
Answered By: Oisin

Here are a few things to consider when loading a service account with environment variables. Notably if you get the error Caused by: "No key could be detected."

  • When copy/pasting the private key, you want to do it directly from the serviceAccount.json file to the file that contains your environment configuration to avoid bad formatting.

  • If you are using a .yml/Jinja2 Ansible variable, you might want to try modifying the private key’s n by \n (inspect the output of your private key)

To make sure the key is actually valid, I used pycryptodome to validate the key. While it is not necessary, it is still good to have.

Here’s an example:

import os
import firebase_admin
from firebase_admin import credentials

from app.core.config import settings
from Crypto.PublicKey import RSA


def init_firebase():  
    private_key_raw = os.environ.get("FIREBASE_PRIVATE_KEY")
    private_key = RSA.importKey(private_key_raw)
    info = {
        # firebase credential details here
        "private_key": private_key.exportKey().decode("utf-8"),
    }
    creds = credentials.Certificate(info)
    firebase_admin.initialize_app(creds)

Note that you might encounter some solutions advising you to do something akin to credentials.Certificate(json.loads(json.dumps(info))), telling you that Certificate does not support anything else than JSON files.

However as of version 5.2.0 (my current version), the source file credentials.py of the firebase_admin package specifies that dict type is supported:

To instantiate a credential from a certificate file, either specify
the file path or a dict representing the parsed contents of the file.

Answered By: Anthony Nguyen
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.