Skip to content

Instantly share code, notes, and snippets.

@Darkflib
Last active August 5, 2025 21:59
Show Gist options
  • Save Darkflib/7763ed90366a2724d6f8468144b9b19b to your computer and use it in GitHub Desktop.
Save Darkflib/7763ed90366a2724d6f8468144b9b19b to your computer and use it in GitHub Desktop.

Revisions

  1. Darkflib created this gist Aug 5, 2025.
    550 changes: 550 additions & 0 deletions gpt-oss.md
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,550 @@
    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.

    ---

    ## 1️⃣ Prerequisites

    | 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` |

    ---

    ## 2️⃣ Installing the Libraries

    ```bash
    # (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 original `pyrebase`. If you hit a bug, try the latest fork.
    ---

    ## 3️⃣ Client‑Side Auth with *Pyrebase*

    ### 3.1 Initialize Pyrebase

    Create a **`firebase_config.py`** (or load from env vars – never commit secrets).

    ```python
    # 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")
    }
    ```

    ```python
    # client.py
    import pyrebase
    from firebase_config import FIREBASE_CONFIG

    firebase = pyrebase.initialize_app(FIREBASE_CONFIG)
    auth = firebase.auth()
    ```

    ### 3.2 Email/Password Sign‑up & Sign‑in

    ```python
    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
    ```

    ### 3.3 Password Reset & Email Verification

    ```python
    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")
    ```

    ### 3.4 Refreshing the ID Token

    ID tokens expire after ~1 hour. Use the *refresh token* to get a fresh one without prompting the user again:

    ```python
    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
    ```

    ### 3.5 Using the Token to Call a Protected API

    When you have an `idToken`, pass it in an `Authorization: Bearer <token>` header to your backend:

    ```python
    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())
    ```

    ---

    ## 4️⃣ Server‑Side Auth with *firebase‑admin*

    The Admin SDK lets you **verify** the ID token sent by the client, **create** users/custom tokens, **manage** accounts, and **set custom claims** (roles).

    ### 4.1 Initialize the Admin SDK

    ```python
    # 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.
    ### 4.2 Verifying an ID Token (Flask Example)

    ```python
    # 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?**

    1. Client (Pyrebase) signs in → receives `idToken`.
    2. Client includes the token in `Authorization: Bearer …`.
    3. Flask endpoint extracts the token and calls `admin_auth.verify_id_token`.
    4. If verification succeeds, you get a dict with `uid`, `email`, `name`, and any **custom claims** you set.

    ### 4.3 Creating a User (Admin)

    ```python
    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).
    ### 4.4 Generating a Custom Token (for OAuth‑like flows)

    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.

    ```python
    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:**

    ```python
    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
    ```

    ### 4.5 Setting & Verifying Custom Claims (Roles)

    ```python
    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:

    ```python
    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:

    ```python
    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!"})
    ```

    ---

    ## 5️⃣ Testing Locally with the **Firebase Authentication Emulator**

    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.

    ### 5️⃣ Setup

    ```bash
    # 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)
    ```

    ### 5️⃣ Running

    ```bash
    firebase emulators:start --only auth
    ```

    You’ll see console output confirming the Auth emulator is listening (default: `http://localhost:9099`).

    ### 5️⃣ Pointing Pyrebase & Admin SDK at the Emulator

    **Pyrebase (client):** Override the `authDomain` & `apiKey` with dummy values— they aren’t used for the emulator, but the SDK still expects them.

    ```python
    # 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.

    ```bash
    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.

    ---

    ## 6️⃣ Common Pitfalls & Best Practices

    | 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`). |

    ### Logging & Monitoring

    * 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).

    ---

    ## 7️⃣ Full Minimal Working Example

    Below is a self‑contained script that:

    1. **Creates** a test user (admin SDK).
    2. **Signs in** with Pyrebase (client).
    3. **Calls** a Flask endpoint with the `idToken`.
    4. **Verifies** the token (admin SDK) and returns the UID.

    ```python
    # ---------------------------------------------------
    # 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:**

    ```bash
    # 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.

    ---

    ## 8️⃣ Going Further

    | 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`). |

    ---

    ## TL;DR Checklist

    1. **Create a Firebase project** → enable required sign‑in providers.
    2. **Add a service‑account JSON** → keep it safe, load with `firebase_admin`.
    3. **Client side** (`pyrebase`):
    * `auth.create_user_with_email_and_password` → sign‑up
    * `auth.sign_in_with_email_and_password` → get `idToken`/`refreshToken`
    * Refresh token when needed.
    4. **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.
    5. **Protect your API**`Authorization: Bearer <idToken>` + token verification.
    6. **Use the Auth Emulator** for local development.
    7. **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! 🚀