Below is a step‑by‑step guide for using Firebase Authentication from Python.
I’ll cover two typical scenarios:
Scenario | What you’ll do in Python | Which library you’ll use |
---|---|---|
Client‑side (desktop / script) – sign‑up, sign‑in, password reset, etc. | Use the Firebase REST API (wrapped in the community library Pyrebase). | pyrebase (or pyrebase4 ) |
Server‑side (backend, admin tasks) – verify ID tokens, create custom tokens, manage users. | Use the Firebase Admin SDK (official). | firebase-admin |
Both can coexist in the same project – the client library talks to Firebase as a regular user, the admin SDK talks as a privileged service account.
Item | Why you need it | How to get it |
---|---|---|
Firebase project | All auth calls are scoped to a project. | Create one at https://console.firebase.google.com |
Enable auth providers (Email/Password, Google, etc.) | Determines what users can use to sign in. | In Authentication → Sign‑in method. |
Service‑account JSON (for admin SDK) | Gives your Python backend privileged access. | Project Settings → Service accounts → Generate new private key. |
Python ≥ 3.8 | Modern syntax, asyncio support, etc. |
Install from python.org or your package manager. |
Virtual environment (optional but recommended) | Keeps dependencies isolated. | python -m venv venv && source venv/bin/activate |
# (inside your virtual env)
pip install pyrebase4 # community wrapper around the Firebase REST API
pip install firebase-admin # official admin SDK
pip install Flask # optional – for a web backend demo
Tip:
pyrebase4
is a maintained fork of the originalpyrebase
. If you hit a bug, try the latest fork.
Create a firebase_config.py
(or load from env vars – never commit secrets).
# firebase_config.py
import os
import json
# You can copy the config object from the Firebase console → Project Settings → General → "Your apps"
FIREBASE_CONFIG = {
"apiKey": os.getenv("FIREBASE_API_KEY"),
"authDomain": os.getenv("FIREBASE_AUTH_DOMAIN"),
"databaseURL": os.getenv("FIREBASE_DATABASE_URL"),
"projectId": os.getenv("FIREBASE_PROJECT_ID"),
"storageBucket": os.getenv("FIREBASE_STORAGE_BUCKET"),
"messagingSenderId": os.getenv("FIREBASE_MESSAGING_SENDER_ID"),
"appId": os.getenv("FIREBASE_APP_ID")
}
# client.py
import pyrebase
from firebase_config import FIREBASE_CONFIG
firebase = pyrebase.initialize_app(FIREBASE_CONFIG)
auth = firebase.auth()
def signup(email: str, password: str):
try:
user = auth.create_user_with_email_and_password(email, password)
print("✅ User created:", user["localId"])
# Optionally send a verification email
auth.send_email_verification(user['idToken'])
return user
except Exception as e:
# Pyrebase throws a generic Exception; unpack the JSON for details.
err = json.loads(e.args[1])
print("❌ Sign‑up error:", err["error"]["message"])
return None
def signin(email: str, password: str):
try:
user = auth.sign_in_with_email_and_password(email, password)
# `user` dict has:
# idToken (JWT for the user, valid ~1h)
# refreshToken (to get a new idToken)
# localId (UID)
print("✅ Signed in! UID:", user["localId"])
return user
except Exception as e:
err = json.loads(e.args[1])
print("❌ Sign‑in error:", err["error"]["message"])
return None
def send_password_reset(email: str):
try:
auth.send_password_reset_email(email)
print("✅ Password‑reset email sent")
except Exception as e:
err = json.loads(e.args[1])
print("❌ Reset error:", err["error"]["message"])
def resend_verification(email: str, password: str):
# Must sign‑in to get a fresh idToken, then call send_email_verification
user = signin(email, password)
if user:
auth.send_email_verification(user['idToken'])
print("✅ Verification email re‑sent")
ID tokens expire after ~1 hour. Use the refresh token to get a fresh one without prompting the user again:
def refresh_id_token(refresh_token: str):
try:
refreshed = auth.refresh(refresh_token)
# refreshed["idToken"] is the new JWT
# refreshed["userId"] is the UID
return refreshed
except Exception as e:
print("❌ Refresh error:", e)
return None
When you have an idToken
, pass it in an Authorization: Bearer <token>
header to your backend:
import requests
def call_protected_endpoint(user):
id_token = user["idToken"]
headers = {"Authorization": f"Bearer {id_token}"}
resp = requests.get("https://my‑api.example.com/profile", headers=headers)
print("Server response:", resp.json())
The Admin SDK lets you verify the ID token sent by the client, create users/custom tokens, manage accounts, and set custom claims (roles).
# admin_init.py
import firebase_admin
from firebase_admin import credentials, auth
# Option 1: set env var GOOGLE_APPLICATION_CREDENTIALS="/path/to/key.json"
# Option 2: load explicitly (preferred for testability)
cred = credentials.Certificate("path/to/serviceAccountKey.json")
firebase_admin.initialize_app(cred)
Important: Never ship the service‑account key with a client‑side app. Keep it on a server‑side environment only.
# server.py
from flask import Flask, request, jsonify
import firebase_admin
from firebase_admin import auth as admin_auth
from admin_init import * # ensures firebase_admin is initialized
app = Flask(__name__)
def verify_token(id_token):
try:
decoded_token = admin_auth.verify_id_token(id_token)
# `uid` is the Firebase UID
return decoded_token
except firebase_admin.exceptions.FirebaseError as e:
# Token invalid, expired, revoked, etc.
print("❌ Token verification failed:", e)
return None
@app.route("/profile")
def profile():
auth_header = request.headers.get("Authorization", "")
if not auth_header.startswith("Bearer "):
return jsonify({"error": "Missing Bearer token"}), 401
id_token = auth_header.split("Bearer ")[1]
decoded = verify_token(id_token)
if not decoded:
return jsonify({"error": "Invalid token"}), 401
uid = decoded["uid"]
# Example: Pull additional data from Firestore/RealtimeDB
return jsonify({
"uid": uid,
"email": decoded.get("email"),
"name": decoded.get("name"),
"customClaims": decoded.get("firebase", {}).get("sign_in_attributes")
})
if __name__ == "__main__":
app.run(debug=True, port=5000)
What’s happening?
- Client (Pyrebase) signs in → receives
idToken
. - Client includes the token in
Authorization: Bearer …
. - Flask endpoint extracts the token and calls
admin_auth.verify_id_token
. - If verification succeeds, you get a dict with
uid
,email
,name
, and any custom claims you set.
def create_user(email: str, password: str, display_name: str = None):
try:
user = admin_auth.create_user(
email=email,
password=password,
display_name=display_name,
email_verified=False,
)
print("✅ Created user:", user.uid)
return user
except firebase_admin.exceptions.FirebaseError as e:
print("❌ Could not create user:", e)
return None
Use case: When you need to provision accounts programmatically (e.g., admin portal, migration from legacy DB).
Suppose you have your own identity provider (e.g., LDAP) and want to let users sign into Firebase using that credential. The server generates a custom token, which the client then exchanges for an idToken
via the client SDK.
def generate_custom_token(uid: str, additional_claims: dict = None) -> str:
"""
Args:
uid: The UID you want the user to have in Firebase.
additional_claims: Optional dict with custom claims (e.g., roles).
Returns:
A signed JWT (custom token) that can be handed to the client.
"""
try:
token = admin_auth.create_custom_token(uid, additional_claims)
# `token` is a bytes object; decode for transport
return token.decode('utf-8')
except firebase_admin.exceptions.FirebaseError as e:
print("❌ Custom token error:", e)
raise
Client side (Pyrebase) to exchange the custom token:
def sign_in_with_custom_token(custom_token: str):
# Pyrebase has a thin wrapper called sign_in_with_custom_token
user = auth.sign_in_with_custom_token(custom_token)
print("✅ Signed in with custom token, UID:", user["localId"])
return user
def set_user_role(uid: str, role: str):
# Role can be anything: 'admin', 'editor', 'beta_tester', …
admin_auth.set_custom_user_claims(uid, {"role": role})
print(f"✅ Set role={role} for uid={uid}")
def get_user_role(uid: str) -> str:
user = admin_auth.get_user(uid)
return user.custom_claims.get('role') if user.custom_claims else None
When you later verify an ID token, those claims appear in the decoded token:
decoded = admin_auth.verify_id_token(id_token)
role = decoded.get('role') # <-- note: top‑level, not under `firebase`
Now you can do RBAC in your Flask middlewares:
def admin_required(f):
@wraps(f)
def wrapper(*args, **kwargs):
token = request.headers.get('Authorization', '').split('Bearer ')[-1]
decoded = verify_token(token)
if not decoded or decoded.get('role') != 'admin':
return jsonify({"error": "admin required"}), 403
return f(*args, **kwargs)
return wrapper
@app.route("/admin/dashboard")
@admin_required
def admin_dashboard():
return jsonify({"msg": "Welcome, admin!"})
Running against production every time is slow and can be risky. Firebase provides an Authentication Emulator that behaves exactly like the real service (including email/password, custom tokens, etc.) but does not send real emails.
# Install the emulator suite (requires Node.js)
npm install -g firebase-tools
# Initialize the emulator config (once)
firebase init emulators
# When prompted, enable the Auth emulator and choose a port (e.g., 9099)
firebase emulators:start --only auth
You’ll see console output confirming the Auth emulator is listening (default: http://localhost:9099
).
Pyrebase (client): Override the authDomain
& apiKey
with dummy values— they aren’t used for the emulator, but the SDK still expects them.
# In firebase_config.py (development)
FIREBASE_CONFIG = {
"apiKey": "fake-api-key",
"authDomain": "localhost",
"databaseURL": "http://localhost:9000",
"projectId": "my-emulated-project",
# The below are not required for auth, but keep them for completeness
"storageBucket": "",
"messagingSenderId": "",
"appId": "fake-app-id"
}
Admin SDK (server): Set the FIREBASE_AUTH_EMULATOR_HOST
environment variable.
export FIREBASE_AUTH_EMULATOR_HOST="localhost:9099"
Now all admin calls (e.g., create_user
, verify_id_token
) hit the emulator instead of production. This is perfect for CI/tests.
Pitfall | Why it happens | How to avoid / fix |
---|---|---|
Storing the idToken forever |
Tokens expire after ~1 h. Using an expired token yields 401 errors. |
Store both idToken and refreshToken . Refresh automatically (auth.refresh(refresh_token) ) before the token expires (e.g., check remaining time via jwt.decode(id_token, options={"verify_signature": False})['exp'] ). |
Hard‑coding service‑account JSON in source control | Accidentally exposing privileged credentials. | Keep the JSON file out of Git (.gitignore ) and load via environment variable (GOOGLE_APPLICATION_CREDENTIALS ) or secrets manager (AWS Secrets Manager, GCP Secret Manager). |
Mixing up Auth vs. Database rules | Assuming Firestore rules are enough for security. | Use Firebase Security Rules for each product (Firestore, Realtime DB, Storage) and verify ID tokens on server if you have additional backend logic. |
Relying on Pyrebase for social sign‑in | Pyrebase only wraps the email/password flow; Google/Facebook sign‑in need the Web SDK or native SDKs. | For social providers, either (a) perform sign‑in on the client (Web, Android, iOS) and send the resulting idToken to your Python backend, or (b) generate a custom token on the server after you have verified the external provider’s token. |
Custom claim size limit (1000 bytes) | Adding too much data leads to INVALID_ARGUMENT errors. |
Store only essential data (role, tier). For richer profile data, keep it in Firestore and reference it by UID. |
Not handling token revocation | Revoked users can still access APIs until the token expires. | Call admin_auth.revoke_refresh_tokens(uid) when you want to force logout, and on the backend check the auth_time claim vs. revoked_at (admin_auth.get_user(uid).tokens_valid_after_timestamp ). |
- Use Google Cloud Logging (via the
google-cloud-logging
library) for critical auth events (sign‑in, token revocation). - Set up Firebase Alerts in the console to be notified about unusual spikes (e.g., many failed sign‑ins).
Below is a self‑contained script that:
- Creates a test user (admin SDK).
- Signs in with Pyrebase (client).
- Calls a Flask endpoint with the
idToken
. - Verifies the token (admin SDK) and returns the UID.
# ---------------------------------------------------
# config.py – keep secrets out of version control
# ---------------------------------------------------
import os
# Set these in your environment or a .env file (use python-dotenv to load)
FIREBASE_API_KEY = os.getenv("FIREBASE_API_KEY")
FIREBASE_PROJECT_ID = os.getenv("FIREBASE_PROJECT_ID")
SERVICE_ACCOUNT_PATH = os.getenv("GOOGLE_APPLICATION_CREDENTIALS") # path to JSON
# ---------------------------------------------------
# admin.py – initialize Admin SDK
# ---------------------------------------------------
import firebase_admin
from firebase_admin import credentials, auth as admin_auth
cred = credentials.Certificate(SERVICE_ACCOUNT_PATH)
firebase_admin.initialize_app(cred)
# ---------------------------------------------------
# client.py – Pyrebase wrapper
# ---------------------------------------------------
import pyrebase
from config import FIREBASE_API_KEY, FIREBASE_PROJECT_ID
pyre_cfg = {
"apiKey": FIREBASE_API_KEY,
"authDomain": f"{FIREBASE_PROJECT_ID}.firebaseapp.com",
"databaseURL": f"https://{FIREBASE_PROJECT_ID}.firebaseio.com",
"projectId": FIREBASE_PROJECT_ID,
"storageBucket": f"{FIREBASE_PROJECT_ID}.appspot.com",
"messagingSenderId": "1234567890",
"appId": "1:1234567890:web:abcdef123456"
}
firebase = pyrebase.initialize_app(pyre_cfg)
auth = firebase.auth()
# ---------------------------------------------------
# server.py – Flask backend
# ---------------------------------------------------
from flask import Flask, request, jsonify
from admin import admin_auth
import firebase_admin.exceptions
app = Flask(__name__)
def verify_id(id_token):
try:
return admin_auth.verify_id_token(id_token)
except firebase_admin.exceptions.FirebaseError as exc:
print("⚠️ Token verify error:", exc)
return None
@app.route("/whoami")
def whoami():
header = request.headers.get("Authorization", "")
if not header.startswith("Bearer "):
return jsonify({"error": "Missing Bearer"}), 401
token = header.split("Bearer ")[1]
decoded = verify_id(token)
if not decoded:
return jsonify({"error": "Invalid token"}), 401
return jsonify({
"uid": decoded["uid"],
"email": decoded.get("email"),
"claims": decoded.get("role") # example custom claim
})
# ---------------------------------------------------
# demo.py – Orchestrates everything
# ---------------------------------------------------
import time
from client import auth
from admin import admin_auth
import requests
TEST_EMAIL = "[email protected]"
TEST_PASSWORD = "SuperSecret123!"
# 1️⃣ Ensure the test user exists
try:
admin_auth.get_user_by_email(TEST_EMAIL)
print("🔎 User already exists")
except firebase_admin.exceptions.NotFoundError:
admin_auth.create_user(email=TEST_EMAIL, password=TEST_PASSWORD)
print("✅ Created test user")
# 2️⃣ Sign in with Pyrebase
user = auth.sign_in_with_email_and_password(TEST_EMAIL, TEST_PASSWORD)
print("🔑 Received idToken (expires in ~1h)")
# 3️⃣ Call Flask backend (run Flask in a separate terminal or thread)
# For demo we start it in a background thread.
import threading
def run_flask():
app.run(port=5005, debug=False)
flask_thread = threading.Thread(target=run_flask, daemon=True)
flask_thread.start()
time.sleep(1) # give Flask a moment to start
# 4️⃣ Make an authorized request
headers = {"Authorization": f"Bearer {user['idToken']}"}
resp = requests.get("http://127.0.0.1:5005/whoami", headers=headers)
print("✅ Backend response:", resp.json())
Run it:
# 1. Set environment variables (or export via .env)
export FIREBASE_API_KEY="AIzaSy...."
export FIREBASE_PROJECT_ID="my-firebase-project"
export GOOGLE_APPLICATION_CREDENTIALS="/path/to/serviceAccountKey.json"
# 2. Install dependencies if you haven’t yet
pip install pyrebase4 firebase-admin Flask python-dotenv requests
# 3. Execute
python demo.py
You should see the user created (or already existing), a successful sign‑in, and a JSON response from the Flask endpoint that includes the UID and email.
Feature | How to add it in Python |
---|---|
Phone‑Number auth | Use the Firebase Auth REST API directly (Pyrebase does not expose it). Look at POST https://identitytoolkit.googleapis.com/v1/accounts:sendVerificationCode and then accounts:signInWithPhoneNumber . |
OAuth (Google, Facebook, Apple) | Perform the OAuth flow on a client (web or mobile) to obtain a Google/Facebook ID token, then call admin_auth.create_custom_token(uid, ...) and send that custom token to the Python client for sign‑in. |
Multi‑factor authentication (MFA) | Currently only the client SDKs (Web/Android/iOS) have first‑class support. In Python you can still verify the 2FA idToken the same way. |
User import/export | Use admin_auth.import_users and admin_auth.list_users . Good for migration from a legacy DB. |
Email templates customization | Configure in the console or via the Firebase Management API (projects.accounts.templates ). Not required for simple use cases. |
Testing with PyTest | Mock auth /admin_auth calls with unittest.mock or spin up the emulator in CI (firebase emulators:exec ). |
- Create a Firebase project → enable required sign‑in providers.
- Add a service‑account JSON → keep it safe, load with
firebase_admin
. - Client side (
pyrebase
):auth.create_user_with_email_and_password
→ sign‑upauth.sign_in_with_email_and_password
→ getidToken
/refreshToken
- Refresh token when needed.
- Server side (
firebase-admin
):admin_auth.verify_id_token(id_token)
→ trust user identity.admin_auth.create_custom_token(uid, claims)
→ integrate external auth.admin_auth.set_custom_user_claims(uid, {"role": "admin"})
→ RBAC.
- Protect your API →
Authorization: Bearer <idToken>
+ token verification. - Use the Auth Emulator for local development.
- Never ship service‑account keys; rotate them regularly.
You now have everything you need to authenticate users, protect endpoints, manage accounts, and integrate custom authentication flows from Python using Firebase. Happy coding! 🚀