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