Secure Cloud Functions With Firebase Authentication (Python)

April 25, 2019
from flask import abort
from firebase_admin import auth

def test_firebase_auth(request):
    authorization = request.headers.get('Authorization')
    # authorization = request.headers['Authorization']
    id_token = None
    if authorization and authorization.startswith('Bearer '):
        id_token = authorization.split('Bearer ')[1]
    else:
        abort(401)

    try:
        decoded_token = auth.verify_id_token(id_token)
        uid = decoded_token['uid']
        # log.info(decoded_token)
        return uid
    except Exception as e: # ValueError or auth.AuthError
        abort(401)

requirements.txt

# https://pypi.org/project/firebase-admin/
firebase-admin==2.16.0

Test Firebase Auth from Web/Javascript

Create another cloud functions to serve html/javascript to perform testing.

from flask import render_template

def test_firebase_auth_ui(request):
    return render_template('test_firebase_auth_ui.html')

Content of templates/test_firebase_auth_ui.html.

  • Make sure test_firebase_auth_ui.html is in templates directory.
  • We shall use Firebase Auth Web/Javascript client with Email/Password authentication for testing.
  • Enable Email/Password Authentication at Firebase Console -> Authentication -> Sign-in method -> Email/Password (Provider).
  • Get firebaseConfig from Firebae Console -> Project Overview -> Add app -> Web.
  • Deploy both test_firebase_auth and test_firebase_auth_ui as cloud functions to perform the testing.
  • Access [SERVER]/test_firebase_auth_ui. Click Create User for first time, then click Sign In buton for subsequent testing. Observe Chrome Console for the messages.
<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">

    <title>Test Firebase Auth</title>
  </head>

  <body>
    <h1>Test Firebase Auth</h1>
    <button id="create-user-button">Create User</button>
    <button id="signin-button">Sign In</button>

    <script src="https://www.gstatic.com/firebasejs/5.10.0/firebase-app.js"></script>
    <script src="https://www.gstatic.com/firebasejs/5.10.0/firebase-auth.js"></script>

    <script>
      var firebaseConfig = {
        apiKey: "AIza...",
        authDomain: "[PROJECT_ID].firebaseapp.com",
        databaseURL: "https://[PROJECT_ID].firebaseio.com",
        projectId: "[PROJECT_ID]",
        storageBucket: "[PROJECT_ID].appspot.com",
        messagingSenderId: "2401..."
      };
      firebase.initializeApp(firebaseConfig);

      document.addEventListener('DOMContentLoaded', function() {
        console.log('init');

        var EMAIL = "test@mydomain.com"
        var PASSWORD = "test1234"
        var TEST_AUTH_URL = '/test_firebase_auth'

        var testCloudFunctionAuth = function(credential) {
            credential.user.getIdToken().then(function(token) {
              var req = new XMLHttpRequest();
              req.onreadystatechange = function() {
                if (this.readyState == XMLHttpRequest.DONE) {
                    if (this.status == 200)
                      console.log(`done: ${this.responseText}`);
                    else
                      console.log(`error: ${this.status}`);
                }
              }
              req.open('GET', TEST_AUTH_URL, true);
              req.setRequestHeader('Authorization', 'Bearer ' + token);
              req.send();
            })
        }

        var createUserButton = document.getElementById('create-user-button');
        createUserButton.addEventListener('click', function() {
          console.log('createUserButton.click')

          firebase.auth().createUserWithEmailAndPassword(EMAIL, PASSWORD).then(function(credential) {

            console.log('create user success');
            testCloudFunctionAuth(credential);

          }).catch(function(error) {
            console.error(`${error.code}: ${error.message}`)
          });
        });

        var signInButton = document.getElementById('signin-button');
        signInButton.addEventListener('click', function() {
          console.log('signInButton.click')

          firebase.auth().signInWithEmailAndPassword(EMAIL, PASSWORD).then(function(credential) {

            console.log('signin success');
            testCloudFunctionAuth(credential);

          }).catch(function(error) {
            console.error(`${error.code}: ${error.message}`)
          });
        });
      });
    </script>
  </body>
</html>

Wrap Firebase Authentication as Python Decorators

from functools import wraps
from flask import abort
from firebase_admin import auth

def firebase_auth_required(f):
    @wraps(f)
    def wrapper(request):
        authorization = request.headers.get('Authorization')
        id_token = None
        if authorization and authorization.startswith('Bearer '):
            id_token = authorization.split('Bearer ')[1]
        else:
            abort(401)

        try:
            decoded_token = auth.verify_id_token(id_token)
        except Exception as e: # ValueError or auth.AuthError
            abort(401)
        return f(request, decoded_token)
    return wrapper

@firebase_auth_required
def test_firebase_auth(request, decoded_token):
    return decoded_token['uid']

References:

This work is licensed under a
Creative Commons Attribution-NonCommercial 4.0 International License.