Authenticating users with Firebase

Region ID

The REGION_ID is an abbreviated code that Google assigns based on the region you select when you create your app. The code does not correspond to a country or province, even though some region IDs may appear similar to commonly used country and province codes. For apps created after February 2020, REGION_ID.r is included in App Engine URLs. For existing apps created before this date, the region ID is optional in the URL.

Learn more about region IDs.

Add a user sign-in flow to your web service that uses Firebase Authentication.

In this step of the guide, you update your web service to authenticate users and to retrieve and display a user's own information after they authenticate. Note that, for this step, the site request times will still be global rather than user-specific.

Before you begin

If you have completed all the previous steps in this guide, skip this section. Otherwise, complete one of the following:

  • Start from Building a Python 3 App and complete all the steps leading up to this one.

  • If you already have a Google Cloud project, you can continue by downloading a copy of the web service and adding Firebase:

    1. Download the sample application repository using Git:

      git clone https://github.com/GoogleCloudPlatform/python-docs-samples
      

      Alternatively, you can download the sample as a zip file and then extract it.

    2. Navigate to the directory that contains a copy of the files from the previous step:

      cd python-docs-samples/appengine/standard_python3/building-an-app/building-an-app-2
      
    3. Add Firebase to your Google Cloud project and web service.

Adding Firebase authentication methods

Firebase provides JavaScript methods and variables that you can use to configure sign-in behavior for your web service. For this web service, add a sign out function, a variable that configures the sign in UI, and a function controlling what changes when a user signs in or out.

To add the behaviors required for an authentication flow, replace your static/script.js file's current event listener method with the following code:

window.addEventListener('load', function () {
  document.getElementById('sign-out').onclick = function () {
    firebase.auth().signOut();
  };

  // FirebaseUI config.
  var uiConfig = {
    signInSuccessUrl: '/',
    signInOptions: [
      // Comment out any lines corresponding to providers you did not check in
      // the Firebase console.
      firebase.auth.GoogleAuthProvider.PROVIDER_ID,
      firebase.auth.EmailAuthProvider.PROVIDER_ID,
      //firebase.auth.FacebookAuthProvider.PROVIDER_ID,
      //firebase.auth.TwitterAuthProvider.PROVIDER_ID,
      //firebase.auth.GithubAuthProvider.PROVIDER_ID,
      //firebase.auth.PhoneAuthProvider.PROVIDER_ID

    ],
    // Terms of service url.
    tosUrl: '<your-tos-url>'
  };

  firebase.auth().onAuthStateChanged(function (user) {
    if (user) {
      // User is signed in, so display the "sign out" button and login info.
      document.getElementById('sign-out').hidden = false;
      document.getElementById('login-info').hidden = false;
      console.log(`Signed in as ${user.displayName} (${user.email})`);
      user.getIdToken().then(function (token) {
        // Add the token to the browser's cookies. The server will then be
        // able to verify the token against the API.
        // SECURITY NOTE: As cookies can easily be modified, only put the
        // token (which is verified server-side) in a cookie; do not add other
        // user information.
        document.cookie = "token=" + token;
      });
    } else {
      // User is signed out.
      // Initialize the FirebaseUI Widget using Firebase.
      var ui = new firebaseui.auth.AuthUI(firebase.auth());
      // Show the Firebase login button.
      ui.start('#firebaseui-auth-container', uiConfig);
      // Update the login state indicators.
      document.getElementById('sign-out').hidden = true;
      document.getElementById('login-info').hidden = true;
      // Clear the token cookie.
      document.cookie = "token=";
    }
  }, function (error) {
    console.log(error);
    alert('Unable to log in: ' + error)
  });
});

Notice that the onAuthStateChanged() method, which controls what changes when a user signs in or out, stores the user's ID token as a cookie. This ID token is a unique token that Firebase generates automatically when a user successfully signs in, and is used by the server to authenticate the user.

Updating your web service to use tokens

Next, verify users on the server using their unique Firebase ID token, then decrypt their token so that you can print their data back to them.

To use the Firebase ID token:

  1. Retrieve, verify, and decrypt the token in the root method of your main.py file:

    from flask import Flask, render_template, request
    from google.auth.transport import requests
    from google.cloud import datastore
    import google.oauth2.id_token
    
    firebase_request_adapter = requests.Request()
    @app.route("/")
    def root():
        # Verify Firebase auth.
        id_token = request.cookies.get("token")
        error_message = None
        claims = None
        times = None
    
        if id_token:
            try:
                # Verify the token against the Firebase Auth API. This example
                # verifies the token on each page load. For improved performance,
                # some applications may wish to cache results in an encrypted
                # session store (see for instance
                # http://flask.pocoo.org/docs/1.0/quickstart/#sessions).
                claims = google.oauth2.id_token.verify_firebase_token(
                    id_token, firebase_request_adapter
                )
            except ValueError as exc:
                # This will be raised if the token is expired or any other
                # verification checks fail.
                error_message = str(exc)
    
            # Record and fetch the recent times a logged-in user has accessed
            # the site. This is currently shared amongst all users, but will be
            # individualized in a following step.
            store_time(datetime.datetime.now(tz=datetime.timezone.utc))
            times = fetch_times(10)
    
        return render_template(
            "index.html", user_data=claims, error_message=error_message, times=times
        )
    
    
  2. Ensure that your requirements.txt file includes all necessary dependencies:

    Flask==3.0.0
    google-cloud-datastore==2.15.1
    google-auth==2.17.3
    requests==2.28.2
    

Testing your web service

Test your web service by running it locally in a virtual environment:

  1. Run the following commands in your project's main directory to install new dependencies and run your web service. If you have not set up a virtual enviornment for local testing, see testing your web service.

    pip install -r requirements.txt
    python main.py
    
  2. Enter the following address in your web browser to view your web service:

    http://localhost:8080
    

Deploying your web service

Now that you have authentication working locally, you can re-deploy your web service to App Engine.

Run the following command from the root directory of your project, where your app.yaml file is located:

gcloud app deploy

All traffic is automatically routed to the new version you deployed.

For more information on managing versions, see Managing Services and Versions.

Viewing your service

To quickly launch your browser and access your web service at https://PROJECT_ID.REGION_ID.r.appspot.com, run the following command:

gcloud app browse

Next steps

Now that you've set up user authentication, you're ready to learn how to update your web service to personalize data for authenticated users.