web
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
- Python.
- Docker: The quickest way to stand up FusionAuth. (There are other ways).
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.
Request flow during login before FusionAuth
The login flow will look like this after FusionAuth is introduced.
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
In the root directory of the repo you’ll find a Docker compose file (docker-compose.yml) and an environment variables configuration file (.env). Assuming you have Docker installed on your machine, you can stand up FusionAuth up on your machine with:
docker compose up -d
This will start three containers, once each for FusionAuth, Postgres, and Elastic.
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 a certain initial state.
If you ever want to reset the FusionAuth system, 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 ispassword
. - Your admin username is
admin@example.com
and the password ispassword
. - The base URL of FusionAuth
http://localhost:9011/
.
You can log into the FusionAuth admin UI and look around if you want, but with Docker/Kickstart you don’t need to.
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 /login
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 with the user’s experience and your application’s integration. This includes
- Hosted pages such as login, registration, email verification, and many more
- Email templates
- User data and custom claims in access token JWTs
Security
- You may want to customize the token expiration times and policies in FusionAuth
- Choose password rules and a hashing algorithm that meet your security needs
Tenant and Application Management
- Model your application topology using Applications, Roles, Groups, Entities, and more
- Set up MFA, Social login, and/or SAML integrations
- Integrate with external systems using Webhooks, SCIM, and Lambdas