web

Python Flask

Python Flask

In this quickstart, you are going to build an application with Python and Flask and integrate it with FusionAuth. You’ll be building it for ChangeBank, a global leader in converting dollars into coins. It’ll have areas reserved for users who have logged in as well as public facing sections.

The Docker Compose file and source code for a complete application are available at https://github.com/FusionAuth/fusionauth-quickstart-python-flask-web.

Prerequisites

This app has been tested with Python 3.8. This example should work with other compatible versions of Python.

General Architecture

While this sample application doesn't have login functionality without FusionAuth, a more typical integration will replace an existing login system with FusionAuth.

In that case, the system might look like this before FusionAuth is introduced.

UserApplicationView HomepageClick Login LinkShow Login FormFill Out and Submit Login FormAuthenticates UserDisplay User's Account or OtherInfoUserApplication

Request flow during login before FusionAuth

The login flow will look like this after FusionAuth is introduced.

UserApplicationFusionAuthView HomepageClick Login Link (to FusionAuth)View Login FormShow Login FormFill Out and Submit Login FormAuthenticates UserGo to Redirect URIRequest the Redirect URIIs User Authenticated?User is AuthenticatedDisplay User's Account or OtherInfoUserApplicationFusionAuth

Request flow during login after FusionAuth

In general, you are introducing FusionAuth in order to normalize and consolidate user data. This helps make sure it is consistent and up-to-date as well as offloading your login security and functionality to FusionAuth.

Getting Started

In this section, you’ll get FusionAuth up and running and use Flask to create a new application.

Clone the Code

First off, grab the code from the repository and change into that directory.

git clone https://github.com/FusionAuth/fusionauth-quickstart-python-flask-web.git
cd fusionauth-quickstart-python-flask-web

Run FusionAuth via Docker

You'll find a Docker Compose file (docker-compose.yml) and an environment variables configuration file (.env) in the root directory of the repo.

Assuming you have Docker installed, you can stand up FusionAuth on your machine with the following.

docker compose up -d

Here you are using a bootstrapping feature of FusionAuth called Kickstart. When FusionAuth comes up for the first time, it will look at the kickstart/kickstart.json file and configure FusionAuth to your specified state.

If you ever want to reset the FusionAuth application, you need to delete the volumes created by Docker Compose by executing docker compose down -v, then re-run docker compose up -d.

FusionAuth will be initially configured with these settings:

  • Your client Id is E9FDB985-9173-4E01-9D73-AC2D60D1DC8E.
  • Your client secret is super-secret-secret-that-should-be-regenerated-for-production.
  • Your example username is richard@example.com and the password is password.
  • Your admin username is admin@example.com and the password is password.
  • The base URL of FusionAuth is http://localhost:9011/.

You can log in to the FusionAuth admin UI and look around if you want to, but with Docker and Kickstart, everything will already be configured correctly.

If you want to see where the FusionAuth values came from, they can be found in the FusionAuth app. The tenant Id is found on the Tenants page. To see the Client Id and Client Secret, go to the Applications page and click the View icon under the actions for the ChangeBank application. You'll find the Client Id and Client Secret values in the OAuth configuration section.

The .env file contains passwords. In a real application, always add this file to your .gitignore file and never commit secrets to version control.

Create your Flask Application

In this section, you’ll set up a basic Flask application with a single page. While this section builds a simple Python / Flask application, you can use the same method to integrate your existing application with FusionAuth.

Set up your Environment

We recommend working in a virtual environment for this.

python3.8 -m venv venv && source venv/bin/activate

Next, create a requirements.txt file to name the project dependencies.

requirements.txt

Authlib==1.2.0
Flask==2.3.2
python-dotenv==1.0.0
requests==2.31.0

And then install the dependencies into your virtual env.

pip install -r requirements.txt

Create the Application

Now create your Flask app, which for now will consist of an environment file named .env and a Python file named server.py.

Create the environment file with these contents:

.env

CLIENT_ID=e9fdb985-9173-4e01-9d73-ac2d60d1dc8e
CLIENT_SECRET=super-secret-secret-that-should-be-regenerated-for-production
ISSUER=http://localhost:9011
APP_SECRET_KEY=0386ffa9-3bff-4c75-932a-48d6a763ce77

In server.py, you’ll add all of the imports you’ll need, declare some constants, and initialize the OAuth library.

server.py

import json
import math
from os import environ as env
from urllib.parse import quote_plus, urlencode
from authlib.integrations.flask_client import OAuth
from dotenv import find_dotenv, load_dotenv
from flask import Flask, redirect, render_template, session, url_for, request, make_response

ACCESS_TOKEN_COOKIE_NAME = "cb_access_token"
REFRESH_TOKEN_COOKIE_NAME = "cb_refresh_token"
USERINFO_COOKIE_NAME = "cb_userinfo"

ENV_FILE = find_dotenv()
if ENV_FILE:
  load_dotenv(ENV_FILE)

app = Flask(__name__)
app.secret_key = env.get("APP_SECRET_KEY")

oauth = OAuth(app)

oauth.register(
  "FusionAuth",
  client_id=env.get("CLIENT_ID"),
  client_secret=env.get("CLIENT_SECRET"),
  client_kwargs={
    "scope": "openid offline_access",
    'code_challenge_method': 'S256' # This enables PKCE
  },
  server_metadata_url=f'{env.get("ISSUER")}/.well-known/openid-configuration'
)

if __name__ == "__main__":
  app.run(host="0.0.0.0", port=env.get("PORT", 5000))

def get_logout_url():
  return env.get("ISSUER") + "/oauth2/logout?" + urlencode({"client_id": env.get("CLIENT_ID")},quote_via=quote_plus)

You should be able to start your Flask application with flask --app server.py --debug run. Note that you won’t be able to access it with a browser yet!

Create a Home Route and a Home Page

The next step is to get a basic home page up and running. This will require a back end route to handle requests to / and a web page template.

In the route function, you’re going to look for an access token, which is created when a user is successfully authenticated. If the user is not authenticated, you’ll just take them to the home page. However, if they are authenticated, you’ll redirect them to an /account page. Since we haven’t implemented login yet, the redirect to /account won’t work.

Add the route handling function to your server.py file.

server.py

@app.route("/")
def home():
  if request.cookies.get(ACCESS_TOKEN_COOKIE_NAME, None) is not None:
    # In a real application, we would validate the token signature and expiration
    return redirect("/account")

  return render_template("home.html")

Next, create the home page in a templates directory. This page just has a login button, which won’t do anything yet. These commands also install a CSS file and an image to make your app look nicer.

mkdir -p templates static/css static/img && \
wget -O templates/home.html https://raw.githubusercontent.com/FusionAuth/fusionauth-quickstart-python-flask-web/main/complete-application/templates/home.html && \
wget -O static/css/changebank.css https://raw.githubusercontent.com/FusionAuth/fusionauth-quickstart-python-flask-web/main/complete-application/static/css/changebank.css && \
wget -O static/img/money.jpg https://raw.githubusercontent.com/FusionAuth/fusionauth-quickstart-python-flask-web/main/complete-application/static/img/money.jpg

With the home page and route complete, you can view the home page in your browser at http://localhost:5000.

Authentication

In this section you’ll build the integration with FusionAuth, which will allow users to log in to your application.

Add OAuth Authentication Routes

Next, you’ll add a /login route that uses authlib to take the user to FusionAuth’s OAuth2 authorize endpoint, and a /callback route that FusionAuth will redirect the user back to after a successful login.

FusionAuth will include an authorization code in the redirect to /callback, and the callback function will make a server-to-server call to FusionAuth to exchange the authorization code for an access token, refresh token, and userinfo object. All of this happens in oauth.FusionAuth.authorize_access_token(). This sequence of redirects and back end calls is known as an OAuth Authorization Code Grant flow.

After your app gets the information back from FusionAuth, you’ll write these items to HTTP-only cookies, so that they will be returned to the Flask application on subsequent requests, but are not readable by code running in the browser.

We also set the userinfo object in the Flask session, to make it easy to use in rendered templates.

Add the /login and /callback routes to your server code.

server.py

@app.route("/login")
def login():
  return oauth.FusionAuth.authorize_redirect(
    redirect_uri=url_for("callback", _external=True)
  )
@app.route("/callback")
def callback():
  token = oauth.FusionAuth.authorize_access_token()

  resp = make_response(redirect("/"))

  resp.set_cookie(ACCESS_TOKEN_COOKIE_NAME, token["access_token"], max_age=token["expires_in"], httponly=True, samesite="Lax")
  resp.set_cookie(REFRESH_TOKEN_COOKIE_NAME, token["refresh_token"], max_age=token["expires_in"], httponly=True, samesite="Lax")
  resp.set_cookie(USERINFO_COOKIE_NAME, json.dumps(token["userinfo"]), max_age=token["expires_in"], httponly=False, samesite="Lax")
  session["user"] = token["userinfo"]

  return resp

Once these steps are done, you should be able to successfully log into your application! Just note that after logging in, you’re taking the user to /account, which doesn’t exist yet.

Add a Secured Page

Remember that after logging in, the application redirects the user to /, which then redirects a logged in user to /account. You are going to add that route and page now.

First, create the page template for a logged in user. It’s going to show a personalized header and a logout button.

wget -O templates/account.html https://raw.githubusercontent.com/FusionAuth/fusionauth-quickstart-python-flask-web/main/complete-application/templates/account.html

Next, create a route to get to that page. This checks if an access token is present. If one isn’t, it forces a logout at FusionAuth. If one is, it renders the /account page.

@app.route("/account")
def account():
  access_token = request.cookies.get(ACCESS_TOKEN_COOKIE_NAME, None)
  refresh_token = request.cookies.get(REFRESH_TOKEN_COOKIE_NAME, None)

  if access_token is None:
    return redirect(get_logout_url())

  return render_template(
    "account.html",
    session=json.loads(request.cookies.get(USERINFO_COOKIE_NAME, None)),
    logoutUrl=get_logout_url())

Now when you log in, you should see the /account page!

Add Logout Support

The last step is to implement logout. When you log a user out of an application, you’ll take them to FusionAuth’s /oauth2/logout endpoint. After logging the user out, FusionAuth will redirect the user to your app’s /logout endpoint, which you’ll create now. This endpoint deletes any cookies that your application created, and clears the Flask session.

@app.route("/logout")
def logout():
  session.clear()

  resp = make_response(redirect("/"))
  resp.delete_cookie(ACCESS_TOKEN_COOKIE_NAME)
  resp.delete_cookie(REFRESH_TOKEN_COOKIE_NAME)
  resp.delete_cookie(USERINFO_COOKIE_NAME)

  return resp

Click the Logout button and watch the browser first go to FusionAuth to log out the user, then return to your home page.

Next Steps

This quickstart is a great way to get a proof of concept up and running quickly, but to run your application in production, there are some things you're going to want to do.

FusionAuth Customization

FusionAuth gives you the ability to customize just about everything to do with the user's experience and the integration of your application. This includes:

Security

Tenant and Application Management