Python.
This app has been tested with Python 3.8. This example should work with other compatible versions of Python.
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.
In this section, you’ll get FusionAuth up and running and use Flask to create a new application.
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
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:
E9FDB985-9173-4E01-9D73-AC2D60D1DC8E
.super-secret-secret-that-should-be-regenerated-for-production
.richard@example.com
and the password is password
.admin@example.com
and the password is password
.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.
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.
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
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 email profile 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!
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
.
In this section you’ll build the integration with FusionAuth, which will allow users to log in to your application.
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.
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!
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.
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 gives you the ability to customize just about everything to do with the user's experience and the integration of your application. This includes: