How to use the App Engine Users service in Flask apps (Module 20)

1. Overview

The Serverless Migration Station series of codelabs (self-paced, hands-on tutorials) and related videos aim to help Google Cloud serverless developers modernize their appications by guiding them through one or more migrations, primarily moving away from legacy services. Doing so makes your apps more portable and gives you more options and flexibility, enabling you to integrate with and access a wider range of Cloud products and more easily upgrade to newer language releases. While initially focusing on the earliest Cloud users, primarily App Engine (standard environment) developers, this series is broad enough to include other serverless platforms like Cloud Functions and Cloud Run, or elsewhere if applicable.

This codelab teaches you how to include the use App Engine Users service to the Module 1 sample app from its codelab. We explain how to implement the App Engine Users service here in Module and migrate that usage to Cloud Identity Platform in Module 21.

You'll learn how to

  • Use the App Engine Users API/bundled service
  • Add Users usage to a basic Python 2 Flask App Engine NDB app

What you'll need

Survey

How will you use this tutorial?

Read it through only Read it and complete the exercises

How would you rate your experience with Python?

Novice Intermediate Proficient

How would you rate your experience with using Google Cloud services?

Novice Intermediate Proficient

2. Background

In preparation to migrate from the App Engine Users service, let's start by adding its usage to the existing Flask app resulting from the Module 1 codelab. That sample app registers web page visits into its Datastore using the App Engine NDB library and displays the top 10 most recent visits to the end-user.

However, the app has no concept of user identities or logins. Your app may produce customized user information which differs per user in which case it becomes a requirement to add authentication to your app. The Users service is a lightweight Google Sign-In (now called Google Identity) client library customized for App Engine that can help you accomplish those goals. In this codelab, we'll integrate its use into the Module 1 sample app.

After this integration, you can migrate the app to the Google Cloud Identity Platform in the next (Module 21) codelab.

This tutorial features the following steps:

  1. Setup/Prework
  2. Update configuration
  3. Modify application code

3. Setup/Prework

This section explains how to:

  1. Set up your Cloud project
  2. Get baseline sample app
  3. (Re)Deploy and validate baseline app

These steps ensure you're starting with working code.

1. Setup project

If you completed the Module 1 codelab, reuse the same project (and code). Alternatively, create a brand new project or reuse another existing project. Ensure the project has an active billing account and an enabled App Engine app. Find your project ID and have it available to use whenever you encounter the PROJECT_ID variable.

2. Get baseline sample app

One of the prerequisites to this codelab is to have a working Module 1 App Engine app. Complete the Module 1 codelab (recommended) or copy the Module 1 app from the repo. Whether you use yours or ours, the Module 1 code is where we'll "START." This codelab walks you through each step, concluding with code that resembles what's in the Module 20 repo folder "FINISH".

Regardless which Module 1 app you use, the folder should look like the output below, possibly with a lib folder as well:

$ ls
README.md               appengine_config.py     requirements.txt
app.yaml                main.py                 templates

3. (Re)Deploy baseline app

Execute the following steps to deploy the Module 1 app:

  1. Delete the lib folder if there is one and run pip install -t lib -r requirements.txt to repopulate it. You may need to use pip2 if you have both Python 2 and 3 installed.
  2. Ensure you have installed and initialized the gcloud command-line tool and reviewed its usage.
  3. If you don't want to enter your PROJECT_ID with each gcloud command issued, set the Cloud project with gcloud config set project PROJECT_ID first.
  4. Deploy the sample app with gcloud app deploy.
  5. Confirm the Module 1 app runs as expected and displays the most recent visits (illustrated below).

a7a9d2b80d706a2b.png

4. Update configuration

No changes are necessary to the standard App Engine configuration files (app.yaml, requirements.txt, appengine_config.py).

5. Modify application code

This section features updates to the following files:

  • main.py — add use of the Users service to the main application
  • templates/index.html — update web template to support user logins (and logouts), including an admin user's "badge"

Imports and constants

The first step is to add an import of the Users service, google.appengine.api.users. The single-line change is illustrated by these code snippets:

BEFORE:

from flask import Flask, render_template, request
from google.appengine.ext import ndb

app = Flask(__name__)

AFTER:

from flask import Flask, render_template, request
from google.appengine.api import users
from google.appengine.ext import ndb

app = Flask(__name__)

Update main handler with new support for user logins

The data model Visit stays the same, as does querying for visits to display in fetch_visits(). Overall, the output remains identical to the Module 1 app. The only change to expect is the display of user login information:

  • If no one is logged in, a generic "Welcome, user" is displayed followed by a "Login" button.
  • If a user has logged in, "Welcome, USER_EMAIL" is displayed, where USER_EMAIL is the email address of the logged in user, followed by a "Logout" button.
  • If the logged in user is an administrator, an "(admin)" badge is displayed next to their email address.

How is adding this functionality possible with the App Engine Users service?

  • The service provides convenient utilities to get login (and logout) links without requiring additional work from the developer.
  • User objects give your app basic information about your users.
  • The Users service also supports the concept of "admin" users, those with developer-selected elevated privileges.
  • An admin user is any logged in user who must have either one of the Cloud IAM basic roles (Viewer, Editor, or Owner) or the App Engine App Admin predefined role. Users who are not members of at least one of these four roles are not considered administrators.

In addition to the most recent visits, much of this user information needs to be passed to the web template which you'll modify shortly. After fetching the most recent visits, the app collates the relevant user information depending on whether the user is logged in or not. This information makes up most of the web template context. The final element to add to the context is a set of most recent visits, matching the Module 1 app. Everything else is new.

Below is the main handler code from Module 1 and the required updates for this Module 20 app:

BEFORE:

@app.route('/')
def root():
    'main application (GET) handler'
    store_visit(request.remote_addr, request.user_agent)
    visits = fetch_visits(10)
    return render_template('index.html', visits=visits)

AFTER:

@app.route('/')
def root():
    'main application (GET) handler'
    store_visit(request.remote_addr, request.user_agent)
    visits = fetch_visits(10)

    # put together users context for web template
    user = users.get_current_user()
    context = {  # logged in
        'who':   user.nickname(),
        'admin': '(admin)' if users.is_current_user_admin() else '',
        'sign':  'Logout',
        'link':  '/_ah/logout?continue=%s://%s/' % (
                      request.environ['wsgi.url_scheme'],
                      request.environ['HTTP_HOST'],
                  ),  # alternative to users.create_logout_url()
    } if user else {  # not logged in
        'who':   'user',
        'admin': '',
        'sign':  'Login',
        'link':  users.create_login_url('/'),
    }

    # add visits to context and render template
    context['visits'] = visits  # display whether logged in or not
    return render_template('index.html', **context)

Note that the updated app creates its own logout URL by piecing together values that come from the Flask request object. We use this technique because the users.create_logout_url() function signs users out of all their Google accounts rather than just logging the user out of the app. See this issue for more information.

Update web template with new display data

The web template templates/index.html requires an update to display all the new user login information passed in from the main handler from the previous steps. Here are the updates to make to your web template:

BEFORE:

<!doctype html>
<html>
<head>
<title>VisitMe Example</title>
<body>

<h1>VisitMe example</h1>
<h3>Last 10 visits</h3>
<ul>
{% for visit in visits %}
    <li>{{ visit.timestamp.ctime() }} from {{ visit.visitor }}</li>
{% endfor %}
</ul>

</body>
</html>

AFTER:

<!doctype html>
<html>
<head>
<title>VisitMe Example</title>
</head>
<body>
<p>
Welcome, {{ who }} <code>{{ admin }}</code>
<button id="logbtn">{{ sign }}</button>
</p><hr>

<h1>VisitMe example</h1>
<h3>Last 10 visits</h3>
<ul>
{% for visit in visits %}
    <li>{{ visit.timestamp.ctime() }} from {{ visit.visitor }}</li>
{% endfor %}
</ul>

<script>
document.getElementById("logbtn").onclick = () => {
    window.location.href = '{{ link }}';
};
</script>
</body>
</html>

This Module 20 sample app now supports use of the App Engine Users service.

6. Summary/Cleanup

This section wraps up this codelab by deploying the app, verifying it works as intended and in any reflected output. Run the worker separately to process the visitor counts. After app validation, perform any clean-up steps and consider next steps.

Deploy and verify application

Deploy your app with gcloud app deploy. The output should be identical to the Module 1 app except that it now features user login elements at the top:

907e64c19ef964f8.png

Note that if no one is logged in, "Welcome, user" is displayed with a "Login" button. When you click it, a login dialog appears. What appears depends on how many Google accounts are available in the browser.

  1. If there are no Google accounts registered with the browser, or only one Google account where the user has not logged in anywhere else, an empty Google Sign-in prompt is displayed: 59cc72e1d80ce63d.png
  2. If there is one Google account, and they signed-in elsewhere, they are automatically logged into this App Engine app, so the user interface changes to displaying a welcome message at the top with the user's email address, and the button changes to "Logout."
  3. If there are multiple Google accounts registered with the browser, an "account-picker" is displayed (see below). When one is selected, if the user has signed-in elsewhere, the email address is shown at the top along with the Logout button. If that user has not signed-in yet, they're prompted for their password, and if successfully entered, their email will be displayed at the top with the Logout button. 84eef9ffe681bdf6.png

Regardless of which scenario the user is in when signing-in, once they've logged in, the screenshot below illustrates how the user's email is displayed as part of the welcome message along with the Logout button:

ad7b59916b69a035.png

If the logged in user is an administrator, a monospaced-font "(admin)" badge is displayed next to their email address, as shown below:

867bcb3334149e4.png

Congratulations for completing this codelab for adding the use of App Engine Users service to the Module 1 sample app successfully. It's now ready for migrating to Cloud Identity Platform, Cloud NDB, and (optionally, to) Python 3 in Module 21.

Clean up

General

If you are done for now, we recommend you disable your App Engine app to avoid incurring billing. However if you wish to test or experiment some more, the App Engine platform has a free quota, and so as long as you don't exceed that usage tier, you shouldn't be charged. That's for compute, but there may also be charges for relevant App Engine services, so check its pricing page for more information. If this migration involves other Cloud services, those are billed separately. In either case, if applicable, see the "Specific to this codelab" section below.

For full disclosure, deploying to a Google Cloud serverless compute platform like App Engine incurs minor build and storage costs. Cloud Build has its own free quota as does Cloud Storage. Storage of that image uses up some of that quota. However, you might live in a region that does not have such a free tier, so be aware of your storage usage to minimize potential costs. Specific Cloud Storage "folders" you should review include:

  • console.cloud.google.com/storage/browser/LOC.artifacts.PROJECT_ID.appspot.com/containers/images
  • console.cloud.google.com/storage/browser/staging.PROJECT_ID.appspot.com
  • The storage links above depend on your PROJECT_ID and LOCation, for example, "us" if your app is hosted in the USA.

On the other hand, if you're not going to continue with this application or other related migration codelabs and want to delete everything completely, shut down your project.

Specific to this codelab

The services listed below are unique to this codelab. Refer to each product's documentation for more information:

Next steps

In this Module 20 codelab, you integrated use of the App Engine Users service to the Module 1 sample app. In the next migration, you will upgrade the app to use the Cloud Identity Platform instead. As of 2021, users are no longer required to migrate to Cloud Identity Platform when upgrading to Python 3. Read more about this in the next section below.

For migrating to Cloud Identity Platform, refer to Module 21 codelab. Beyond that are additional migrations to consider, such as Cloud Datastore, Cloud Memorystore, Cloud Storage, Cloud Tasks (push queues), or Cloud Pub/Sub (pull queues). There are also cross-product migrations to Cloud Run and Cloud Functions. All Serverless Migration Station content (codelabs, videos, source code [when available]) can be accessed at its open source repo.

7. *Migration to Python 3

In Fall 2021, the App Engine team extended support of many of the bundled services to 2nd generation runtimes (that have a 1st generation runtime). As a result, you are no longer required to migrate from bundled services like App Engine Users to standalone Cloud or 3rd-party services like Cloud Identity Platform when porting your app to Python 3. In other words, you can continue using Users in Python 3 App Engine apps so long as you retrofit the code to access bundled services from next-generation runtimes.

You can learn more about how to migrate bundled services usage to Python 3 in the Module 17 codelab and its corresponding video. While that topic is out-of-scope for Module 20, linked below are Python 3 versions of the Module 1 app ported to Python 3 and still using App Engine NDB. (At some point, a Python 3 version of the Module 20 app will also be made available.)

8. Additional resources

Listed below are additional resources for developers further exploring this or related Migration Module as well as related products. This includes places to provide feedback on this content, links to the code, and various pieces of documentation you may find useful.

Codelab issues/feedback

If you find any issues with this codelab, please search for your issue first before filing. Links to search and create new issues:

Migration resources

Links to the repo folders for Module 1 (START) and Module 20 (FINISH) can be found in the table below. They can also be accessed from the repo for all App Engine codelab migrations; clone it or download a ZIP file.

Codelab

Python 2

Python 3

Module 1

code

code (not featured in this tutorial)

Module 20 (this codelab)

code

N/A

Online references

Below are resources relevant for this tutorial:

App Engine general docs

Other Google Cloud information

Videos

License

This work is licensed under a Creative Commons Attribution 2.0 Generic License.