Protecting your Python API with Firebase Auth

Suppose you’re using Firebase to authenticate users on your client webapp, but then you add a backend API that you would like to protect with Firebase authentication as well. In this article, I will show you how to protect your Python Flask API’s endpoints with Firebase authentication using the Firebase Admin SDK.

  1. Setup
  2. Process Authorization header in Python
  3. Pass the token in from the webapp

Setup

First, we install the firebase libraries using:

pip install firebase-admin
npm install firebase

Next, we need to setup the Firebase service account key that will allow our backend API to make calls to Firebase. To do that, first we set up a service account, and download the service account key JSON file by following the instructions in the Firebase documentation.

Then, you take the JSON file and place it somewhere on your computer and set the GOOGLE_APPLICATION_CREDENTIALS environment variable. Finally, you initialize Firebase in your Python app, by adding the following line somewhere in you application:

from firebase_admin import initialize_app

_FIREBASE_APP = initialize_app()

Process Authorization header in Python

When Firebase authenticates a user for you, it generates a user token that can be used to make calls to Firebase on the user’s behalf. So, to check that a user is authenticated on the API, we will pass the token from the webapp in the Authorization header of every request. To process the header in Python, we will define a get_current_user function:

from typing import Optional
from flask import request
from firebase_admin.auth import verify_id_token

def get_current_user() -> Optional[str]:
  # Return None if no Authorization header.
  if "Authorization" not in request.headers:
    return None
  authorization = request.headers["Authorization"]

  # Authorization header format is "Bearer <token>".
  # This matches OAuth 2.0 spec: 
  # https://www.rfc-editor.org/rfc/rfc6750.txt.
  if not authorization.startswith("Bearer "):
    return None

  token = authorization.split("Bearer ")[1]
  try:
    # Verify that the token is valid.
    result = verify_id_token(token)
    # Return the user ID of the authenticated user.
    return result["uid"]
  except:
    return None

Then, in any of the Flask endpoints that require authentication, we can simply check that get_current_user returns a valid user ID:

from flask import Flask, make_response

app = Flask(__name__)

@app.route("/api/resource")
def run_endpoint():
  user = get_current_user()
  if not user:
    return make_response("Unauthorized", 401)

  # do something

Pass the token in from the webapp

Now, we’ll discuss how to propagate the token from the React webapp to the API. When Firebase authenticates a user on your app, it will also issue an ID token. We will listen for this token, and maintain it in a Context so that we can always have an up-to-date token when we make the request to our Python backend.

import { initializeApp } from "firebase/app";
import { getAuth } from "firebase/auth";
import {
  ReactNode,
  createContext,
  useContext,
  useEffect,
  useState,
} from "react";

const firebaseApp = initializeApp({
  // Follow the instructions at
  // https://support.google.com/firebase/answer/7015592
  // to get your Firebase config object.
});

const TokenContext = createContext<string>("");
const TokenProvider(props: { children: ReactNode }) {
  const [idToken, setIdToken] = useState("");

  // Register a listener for the ID token.
  useEffect(() => {
    const auth = getAuth(firebaseApp);
    auth.onIdTokenChanged(async (user) => {
      setIdToken((await user?.getIdToken()) ?? "");
    });
  }, []);

  return (
    <TokenContext.Provider value={idToken}>
      {props.children}
    </TokenContext.Provider>
  )
}

function useToken() {
  return useContext(TokenContext);
}

The TokenProvider will be used to make the token available anywhere in your app. To do so, you just need to wrap your app component with the TokenProvider component, like so:

<TokenProvider>
  <App/>
</TokenProvider>

Then, in any component that will make HTTP requests, you can get the latest context like so:

function SomeComponent() {
  const token = useToken();

  return <div>component</div>;
}

To make HTTP requests with our token, we just pass it in via the Authorization header as we discussed earlier. If we use something like Axios, this is straightforward:

import axios from "axios";

function SomeComponent() {
  const token = useToken();

  const handleClick = async () => {
    const headers = { 
      Authorization: `Bearer ${token}` 
    };
    await axios.get("/api/resource", { headers });
  };

  return (
    <button onClick={handleClick}>
      Click me!
    </button>
  );
}