Authenticating Users on App Engine Using Firebase

This tutorial shows how to retrieve, verify, and store user credentials using
Firebase Authentication, the
App Engine standard environment, and Datastore.

This document walks you through a simple note-taking application called
Firenotes that stores users' notes in their own personal notebooks. Notebooks
are stored per user, and identified by each user's unique
Firebase Authentication ID. The application has the following components:

The frontend configures the sign-in user interface and retrieves the
Firebase Authentication ID. It also handles authentication state changes and lets
users see their notes.

FirebaseUI is an
open-source, drop-in solution that simplifies authentication
and UI tasks. The SDK handles user login, linking multiple
providers to one account, recovering passwords, and more. It implements
authentication best practices for a smooth and secure sign-in experience.

The backend verifies the user's authentication state and returns user
profile information as well as the user's notes.

The application stores user credentials in Datastore by using the
NDB client library,
but you can store the credentials in a database of your choice.

The following diagram shows how the frontend and backend communicate with each
other and how user credentials travel from Firebase to the
database.

Firenotes is based on the
Flask
web application framework. The sample app uses Flask because of its simplicity
and ease of use, but the concepts and technologies explored are applicable
regardless of which framework you use.

Objectives

By completing this tutorial, you'll accomplish the following:

Configure the Firebase Authentication user interface.

Obtain a Firebase ID token and verify it using server-side
authentication.

Store user credentials and associated data in Datastore.

Query a database using the NDB client library.

Deploy an app to App Engine.

Costs

This tutorial uses billable components of Google Cloud, including:

Datastore

Use the Pricing Calculator
to generate a cost estimate based on your projected usage.
New Google Cloud users might be
eligible for a free trial.

In the Cloud Console, on the project selector page,
select or create a Cloud project.

Note: If you don't plan to keep the
resources that you create in this procedure, create a project instead of
selecting an existing project. After you finish these steps, you can
delete the project, removing all resources associated with the project.

If you have already installed and initialized the SDK to a different project,
set the gcloud project to the App Engine project ID you're using
for Firenotes. See Managing Cloud SDK Configurations for
specific commands to update a project with the gcloud tool.

Enable the providers you have chosen to keep in the
Firebase console by clicking Authentication >
Sign-in method. Then, under Sign-in providers, hover the cursor over
a provider and click the pencil icon.

Toggle the Enable button and, for third-party identity providers,
enter the provider ID and secret from the provider's developer site. The
Firebase docs give specific instructions in the
"Before you begin" sections of the
Facebook,
Twitter,
and GitHub
guides. After enabling a provider, click Save.

In the Firebase console, under Authorized Domains,
click Add Domain and enter the domain of your app on
App Engine in the following format:

[PROJECT_ID].appspot.com

Do not include http:// before the domain name.

Installing dependencies

Navigate to the backend directory and complete the application setup:

cd backend/

Install the dependencies into a lib directory in your project:

pip install -t lib -r requirements.txt

In appengine_config.py, the vendor.add() method registers the libraries in
the lib directory.

Running the application locally

To run the application locally, use the App Engine local development
server:

Add the following URL as the backendHostURL in main.js:

http://localhost:8081

Navigate to the root directory of the application. Then, start the
development server:

When a user is signed in, the Firebase getToken() method in the
callback returns a Firebase ID token in the form of a JSON Web
Token (JWT).

Verifying tokens on the server

After a user signs in, the frontend service fetches any existing notes in the
user's notebook through an AJAX GET request. This requires authorization to
access the user's data, so the JWT is sent in the Authorization header of the
request using the Bearer schema:

Each identity provider sends a different set of claims, but each has at least a
sub claim with a unique user ID and a claim that provides some profile
information, such as name or email, that you can use to personalize the user
experience on your app.

Managing user data in Datastore

After authenticating a user, you need to store their data for it to persist
after a signed-in session has ended. The following sections explain how to store
a note as a Datastore entity and segregate entities by user ID.

Creating entities to store user data

You can create an entity in Datastore by declaring an
NDB model class with
certain properties such as integers or strings. Datastore indexes
entities by kind; in the case of Firenotes, the kind of each entity is Note.
For querying purposes, each Note is stored with a key name, which is the
user ID obtained from the sub claim in the previous section.

The following code demonstrates how to set properties of an entity, both with
the constructor method for the model class when the entity is created and
through assignment of individual properties after creation:

data = request.get_json()
# Populates note properties according to the model,
# with the user ID as the key name.
note = Note(
parent=ndb.Key(Note, claims['sub']),
message=data['message'])
# Some providers do not provide one of these so either can be used.
note.friendly_id = claims.get('name', claims.get('email', 'Unknown'))

To write the newly created Note to Datastore, call the put()
method on the note object.

Retrieving user data

To retrieve user data associated with a particular user ID, use the NDB
query() method to search the database for notes in the same entity group.
Entities in the same group, or
ancestor path,
share a common key name, which in this case is the user ID.

Note: Datastore indexes take a few minutes to update, so the
application might not be fully functional immediately after deployment.

Cleaning up

To avoid incurring charges to your Google Cloud account for the
resources used in this tutorial, delete your App Engine project:

Deleting the project

The easiest way to eliminate billing is to delete the project that you
created for the tutorial.

To delete the project:

Caution: Deleting a project has the following effects:

Everything in the project is deleted. If you used an existing project for
this tutorial, when you delete it, you also delete any other work you've done in the project.

Custom project IDs are lost.
When you created this project, you might have created a custom project ID that you want to use in
the future. To preserve the URLs that use the project ID, such as an appspot.com
URL, delete selected resources inside the project instead of deleting the whole project.

If you plan to explore multiple tutorials and quickstarts, reusing projects can help you avoid
exceeding project quota limits.