fastapi logo
python logo

FastAPI Authentication by Example

Published on December 5, 2024
Photo of Jessica Temporal
Jessica TemporalSr. Developer Advocate
Learning GoalThis FastAPI guide will teach you how to secure a FastAPI web application using token-based authentication.

In this guide, you'll learn how to integrate Auth0 by Okta with FastAPI to implement the following security features:

  • Adding user login, sign-up, and logout to a FastAPI application.
  • Retrieving and using user profile information to personalize FastAPI pages.
  • Protecting FastAPI routes from unauthorized access.
  • Making API calls from FastAPI to request data from an external protected API.

This guide uses the Authlib library, which provides developers with a high-level API to handle OAuth and OpenID Connect servers from a low level specification implementation to integration with the major Python frameworks.

How Does Auth0 Work?

With the help of Auth0 by Okta, you don't need to be an expert on identity protocols such as OAuth 2.0 or OpenID Connect, to understand how to secure your web application stack.

First you integrate your FastAPI application with Auth0. Your application will then redirect users to an Auth0 customizable login page where they can log in using their preferred method. Once your users log in successfully, Auth0 redirects them back to your FastAPI app, returning JSON Web Tokens (JWTs) with their authentication and user information.

Get the Starter Application

We have created a starter project to help you learn FastAPI security concepts through hands-on practice. You can focus on building FastAPI routes and services to secure your application.

Start by cloning the web-app_fastapi_python_hello-world repository on its starter branch:

COMMAND
git clone -b starter [email protected]:oktadev/auth0-web-app_fastapi_python_hello-world.git
At the very end of this guide you'll find the information on how to get the complete sample code.

Once you have the cloned repo, make web-app_fastapi_python_hello-world your current directory:

COMMAND
cd web-app_fastapi_python_hello-world

Next, to work with the project you will create a Python virtual environment:

COMMAND
python3 -m venv .env
Feel free to use your preferred way to create Python virtual environments.

Then activate your environment

LOADING...

And finally upgrade pip and install the project dependencies like so:

COMMAND
pip install -U pip && pip install -r requirements.txt

Now is time to run the project. This starter FastAPI app is a functional application with routes and services that hydrates the user interface. It is important to notice that the message service implemented in the starter project simulates the external API by hardcoding the responses. Later on this guide, you'll integrate this FastAPI application with a real API server using a backend technology of your choice.

Run the following command to get your FastAPI application running on port 4040:

COMMAND
uvicorn main:app --reload --port 4040

You'll see the output on your terminal like so:

INFO: Will watch for changes in these directories: ['~/web-app_fastapi_python_hello-world']
INFO: Uvicorn running on http://127.0.0.1:4040 (Press CTRL+C to quit)
INFO: Started reloader process [6212] using WatchFiles
INFO: Started server process [6214]
INFO: Waiting for application startup.
INFO: Application startup complete.

Now you should visit http://127.0.0.1:4040/ on your browser to access the running application and you should see somehting like this:

fastapi web app homepage

Configure FastAPI with Auth0

Follow these steps to get started with the Auth0 Identity Platform quickly:

Sign up and create an Auth0 Application

A free account also offers you:

During the sign-up process, you'll create something called an Auth0 Tenant, representing the product or service to which you are adding authentication.

Once you sign in, Auth0 takes you to the Dashboard. In the left sidebar menu, click on "Applications".

Then, click the "Create Application" button. A modal opens up with a form where you can provide a name for the application and choose its type. Use the following values:

Name
Auth0 FastAPI Web Application
Application Type
Regular Web Applications
Regular Web Applications

Click the "Create" button to complete the process. Your Auth0 application page loads up.

In the next step, you'll learn how to help FastAPI and Auth0 communicate.

What's the relationship between Auth0 Tenants and Auth0 Applications?

Let's say that you have a photo-sharing FastAPI app called "FastAPIgram". You then would create an Auth0 tenant called fastapigram. From a customer perspective, FastAPIgram is that customer's product or service.

Now, say that FastAPIgram is available on three platforms: web as a web application and Android and iOS as a native mobile application. If each platform needs authentication, you need to create three Auth0 applications to provide the product with everything it needs to authenticate users through that platform.

FastAPIgram users belong to the Auth0 FastAPIgram tenant, which shares them across its Auth0 applications.

Create a communication bridge between FastAPI and Auth0

When using the Auth0 Identity Platform, you don't have to build login and signup forms. Auth0 offers a Universal Login Page to reduce the overhead of adding and managing authentication.

How does Universal Login work?

Your FastAPI application will redirect users to Auth0 whenever they trigger an authentication request. Auth0 will present them with a login page. Once they log in, Auth0 will redirect them back to your FastAPI application. For that redirecting to happen securely, you must specify in your Auth0 Application Settings the URLs to which Auth0 can redirect users once it authenticates them.

As such, click on the "Settings" tab of your Auth0 Application page, locate the "Application URIs" section, and fill in the following values:

Allowed Callback URLs
http://127.0.0.1:4040/callback

The above value is the URL that Auth0 can use to redirect your users after they successfully log in.

Allowed Logout URLs
http://127.0.0.1:4040

The above value is the URL that Auth0 can use to redirect your users after they log out.

Allowed Web Origins
http://127.0.0.1:4040

Click the "Save Changes" button at the bottom of the page.

Do not close this page yet. You'll need some of its information in the next section.

Add the Auth0 configuration variables to FastAPI

From the application Settings page on the Auth0 Dashboard, you need the Auth0 Domain and Client ID values to allow your FastAPI application to use the communication bridge you created.

What exactly is an Auth0 Domain, an Auth0 Client ID and a Auth0 Client Secret?

Domain

When you created a new Auth0 account, Auth0 asked you to pick a name for your tenant. This name, concatenated with .auth0.com, is your Auth0 Domain. It's the base URL that you will use to access the Auth0 APIs and the URL where you'll redirect users to log in.

You can also use custom domains to allow Auth0 to do the authentication heavy lifting for you without compromising your branding experience.

Client ID

Each application is assigned a Client ID upon creation, which is an alphanumeric string, and it's the unique identifier for your application (such as q8fij2iug0CmgeLfTfG1tSGdSQyGaTUA). You cannot modify the Client ID. You will use the Client ID to identify the Auth0 Application to which the FastAPI application needs to connect.

Warning: Another critical piece of information present in the "Settings" is the Client Secret. This secret protects your resources by only granting tokens to requestors if they're authorized. Think of it as your application's password, which must be kept confidential at all times. If anyone gains access to your Client Secret, they can impersonate your application and access protected resources.

Head back to your Auth0 application page and click on the "Settings" tab.

Locate the "Basic Information" section and follow these steps to get the Auth0 Domain, Auth0 Client ID, and Auth0 Client Secret values:

Auth0 application settings to enable user authentication

When you enter a value in the input fields present on this page, any code snippet that uses such value updates to reflect it. Using the input fields makes it easy to copy and paste code as you follow along.

As such, enter the "Domain", "Client ID", and "Client Secret" values in the following fields to set up your web application in the next section:

For security, these configuration values are stored in memory and only used locally. They are gone as soon as you refresh the page! As an extra precaution, you should use values from an Auth0 test application instead of a production one.

These variables let your FastAPI application identify as an authorized party to interact with the Auth0 authentication server.

Now, create a new .config file under the FastAPI project directory as follows:

.config
[AUTH0]
CLIENT_ID=AUTH0-CLIENT-ID
CLIENT_SECRET=AUTH0-CLIENT-SECRET
DOMAIN=AUTH0-DOMAIN
[WEBAPP]
SESSION_SECRET=
You can also rename the .example.config to .config and replace the content of the file with the details above.

Now you need to fill the missing information for the SESSION_SECRET. To do so, generate a string using openssl using the following command:

COMMAND
openssl rand -hex 32

The openssl CLI tool will generate a random string that you can use as session secret. This string is used to encode the session data.

Load variables to your environment

For these variables to be available to your application you'll need to load them. Go to the config.py file in the root of the project and use the pre-defined function to do so:

config.py
# ... existing code
def load_config():
"""
Loads configuration from .config file
"""
config = configparser.ConfigParser()
config.read(".config")
return config
config = load_config() # 👈 new code
# ... existing code

Keep in mind that this application is using the Python configparser module to load variables from .config.

Now you are all set to use all of these variables in the next steps.

Define the Session Middleware

To be able to access the session data you'll need to use the Starlette SessionMiddleware. This allows you to use access the data using the request.session dictionary that you'll see in coming steps when logging in a given user.

Define the session middleware like so:

config.py
# ... existing code
from fastapi.templating import Jinja2Templates
from starlette.middleware.sessions import SessionMiddleware # 👈 new code
# ... existing code
def create_app():
app = FastAPI()
app.mount("/static", StaticFiles(directory="static"), name="static")
# 👇 new code
app.add_middleware(
SessionMiddleware,
secret_key=config['WEBAPP']['SESSION_SECRET']
)
# 👆 new code
return app
# ... existing code

Now your app has a session you can access.

Set up the OAuth and OpenID Connect with Authlib

To integrate your FastAPI application with Auth0 you'll use the Authlib library, which will handle all the OAuth and OpenID Connect for you.

Initialize Auth0 configuration

So far your application registers one API Router that is responsible for the application routes like / and /profile.

To set up Auth0 you'll register a new API router that will take care of all auth routes and logic.

Start by creating a new directory at the root of your project named auth:

COMMAND
mkdir auth

And add the following empty files inside of the auth/ directory:

COMMAND
touch auth/__init__.py auth/config.py auth/routes.py

Now you can start by setting up authlib and register a new Auth0 OAuth provider by adding the following code to the auth/config.py file:

auth/config.py
from authlib.integrations.starlette_client import OAuth
from config import config
"""Storing the configuration into the `auth0_config` variable for later usage"""
auth0_config = config['AUTH0']
oauth = OAuth()
oauth.register(
"auth0",
client_id=auth0_config['CLIENT_ID'],
client_secret=auth0_config['CLIENT_SECRET'],
client_kwargs={
"scope": "openid profile email",
},
server_metadata_url=f'https://{auth0_config["DOMAIN"]}/.well-known/openid-configuration'
)

Once you provide authlib with the Auth0 server details such as the client_id, client_secrent, scope, and server_metadata_url, the library is fully set up and you can start creating routes to handle the authentication requests with the Auth0 Authentication Server.

What is the server_metadata_url?

When setting up the OAuth client to use Auth0 in your FastAPI application, you'll need to pass along the URL that holds the OpenID configuration for your tenant.

The OpenID configuration URL provides the Authlib OAuth() client with all the references to communicate with Auth0 and validate the tokens that it will receive.

The Authlib library bundles together OAuth and OpenID Connect (OIDC) to make it easier for you while setting up OAuth and OIDC. If you pass "openid" as part of your "scope" you need to pass the configuration URL in the server_metadatada_url.

Read "Obtaining OpenID Provider Configuration Information" from the OpenID Connect spec to learn more about OpenID Connect configuration information and discovery. To understand more about how Authlib handles OpenID in FastAPI read FastAPI OpenID Connect Client.

What about using scopes?

You'll notice you are passing scope as part of the OAuth client arguments. Here you're providing the OpenID Connect Scopes: openid profile email.

  • openid: This scope informs the Auth0 Authorization Server that the Client is making an OpenID Connect (OIDC) request to verify the user's identity. OpenID Connect is an authentication protocol.

  • profile: This scope value requests access to the user's default profile information, such as name, nickname, and picture.

  • email: This scope value requests access to the email and email_verified information.

The details of the OpenID Connect Scopes go into the ID Token. However, you can define custom API scopes to implement access control. You'll identify those custom scopes in the calls that your client applications make to that API. Auth0 includes API scopes in the access token as the scope claim value.

The concepts about API scopes or permissions are better covered in an Auth0 API tutorial such as "Use TypeScript to Create a Secure API with Node.js and Express: Role-Based Access Control".

Create the auth API Router

Now populate the auth/routes.py with the following content:

auth/routes.py
from fastapi import APIRouter, Request
auth_router = APIRouter()
@auth_router.get("/login")
async def login(request: Request):
"""
Redirects the user to the Auth0 Universal Login (https://auth0.com/docs/authenticate/login/auth0-universal-login)
"""
return {"message": "Login"}
@auth_router.get("/signup")
async def signup(request: Request):
"""
Redirects the user to the Auth0 Universal Login (https://auth0.com/docs/authenticate/login/auth0-universal-login)
"""
return {"message": "Sign Up"}
@auth_router.get("/logout")
def logout(request: Request):
"""
Redirects the user to the Auth0 Universal Login (https://auth0.com/docs/authenticate/login/auth0-universal-login)
"""
return {"message": "Logout"}
@auth_router.get("/callback")
async def callback(request: Request):
"""
Callback redirect from Auth0
"""
return {"message": "Callback"}

These routes will hold the logic that makes your application interact with Auth0. You will update and understand each one of them shortly.

Register the auth API Router

Now to register the auth router with the main application update the main.py file in the root of your project like so:

main.py
from config import app
from auth.routes import auth_router # 👈 new code
from webapp.routes import webapp_router
app.include_router(auth_router) # 👈 new code
app.include_router(webapp_router)

This allow your application to have access to the new auth endpoints you just created.

Update the Auth0 callback route

With the Auth0 configuration implemented and the auth API Router registered, first you'll need to update the route that handles the authentication callback.

auth/routes.py
from fastapi import APIRouter, Request # ... existing code
from fastapi.responses import RedirectResponse # 👈 new code
from auth.config import oauth # 👈 new code
# ... existing code
@auth_router.get("/callback")
async def callback(request: Request):
"""
Callback redirect from Auth0
"""
# 👇 new code
token = await oauth.auth0.authorize_access_token(request)
# Store `id_token`, and `userinfo` in session
request.session['id_token'] = token['id_token']
request.session['userinfo'] = token['userinfo']
# 👆 new code
return RedirectResponse(url=request.url_for("profile")) # 👈 updated code

After the user attempts to log in, either if it was successful or not, Auth0 will redirect them to the /callback endpoint.

In the callback route you authenticate the user coming from the request, store the ID (user) token in session and redirect the user to the profile endpoint. When there's an error, the application will respond with a 400 error status code.

In the following steps you'll update the login, logout, and sign up routes as well as the templates to use them.

Create the Login Button

You have created the callback route to handle a login flow in your app. Now you need to create a Login Button that will trigger the authentication flow.

Create a new template that will handle the authentication buttons in the navigation bar:

COMMAND
touch templates/navigation/desktop/nav_bar_buttons.html

Next, add the following content:

templates/navigation/desktop/nav_bar_buttons.html
<div class="nav-bar__buttons">
<a href="{{ url_for('login') }}" class="button__login" >Log In</a>
</div>

You'll create an additional file for the mobile template:

COMMAND
touch templates/navigation/mobile/mobile_nav_bar_buttons.html

Now, and add the following content:

templates/navigation/mobile/mobile_nav_bar_buttons.html
<div class="mobile-nav-bar__buttons">
<a href="{{ url_for('login') }}" class="button__login" >Log In</a>
</div>

In both templates you are creating a button labeled "Log In". When the users click on this button they will make a GET request to /login endpoint.

Right now this endpoint will show a JSON object with a temporary message {"message": "Login"}. You'll adjust this logic in the next section.

But before you do that, add the new button to the navigation bar.

For the Desktop template, make the following changes:

templates/navigation/desktop/nav_bar.html
<div class="nav-bar__container">
<nav class="nav-bar">
{% include 'navigation/desktop/nav_bar_brand.html' %}
{% include 'navigation/desktop/nav_bar_tabs.html' %}
{% include 'navigation/desktop/nav_bar_buttons.html' %} <!-- 👈 new code -->
</nav>
</div>

And similarly for mobile:

templates/navigation/mobile/mobile_nav_bar.html
<div class="mobile-nav-bar__container">
<nav id="mobile-nav-bar" class="mobile-nav-bar">
{% include 'navigation/mobile/mobile_nav_bar_brand.html' %}
{% include 'navigation/mobile/menu_button.html' %}
<div id="mobile-menu" class="mobile-nav-bar__menu mobile-nav-bar__menu--closed">
{% include 'navigation/mobile/mobile_nav_bar_tabs.html' %}
{% include 'navigation/mobile/mobile_nav_bar_buttons.html' %} <!-- 👈 new code -->
</div>
</nav>
</div>

Update the login route

For the user to be able to authenticate you need to update the /login logic as follows:

auth/routes.py
from fastapi import APIRouter, Request
from fastapi.responses import RedirectResponse
from auth.config import auth0_config, oauth # 👈 updated code
# ... existing code
@auth_router.get("/login")
async def login(request: Request):
"""
Redirects the user to the Auth0 Universal Login (https://auth0.com/docs/authenticate/login/auth0-universal-login)
"""
# 👇 new code
if 'id_token' not in request.session:
return await oauth.auth0.authorize_redirect(
request,
redirect_uri=request.url_for("callback")
)
# 👆 new code
return RedirectResponse(url=request.url_for("profile")) # 👈 updated code
# ... existing code

If you run your server and look at the main page http://127.0.0.1:4040, now you should see the Log In button on the navigation bar like so:

Main page with login button

When the user presses the "Log In" button from the UI, the login route will redirect the user to the Auth0 Universal Login Page, where the user will be able to authenticate. After completion Auth0 will redirect the user to the callback endpoint completing the authentication process.

Universal Login Page

Create the Sign-Up Button

Users are able to log in, but they also need to be able to Sign Up.

Register the signup route

Similarly to when you updated the login route, you need to adjust the signup route. The process only differs in that you will pass an additional parameter to Auth0, hinting the service to start the process on the Sign Up page as opposed to the Log In page.

auth/routes.py
# ... existing code
@auth_router.get("/signup")
async def signup(request: Request):
"""
Redirects the user to the Auth0 Universal Login (https://auth0.com/docs/authenticate/login/auth0-universal-login)
"""
# 👇 new code
if 'id_token' not in request.session: # it could be userinfo instead of id_token
return await oauth.auth0.authorize_redirect(
request,
redirect_uri=request.url_for("callback"),
screen_hint="signup"
)
# 👆 new code
return RedirectResponse(url=request.url_for("profile")) # 👈 updated code
# ... existing code

When the user presses the "Sign Up" button, they will be redirected to a sign-up page instead of a login page.

Add the sign up button to the template

Open the templates/navigation/desktop/nav_bar_buttons.html and add a new button:

templates/navigation/desktop/nav_bar_buttons.html
<div class="nav-bar__buttons">
<a href="{{ url_for('signup') }}" class="button__sign-up">Sign Up</a> <!-- 👈 new code -->
<a href="{{ url_for('login') }}" class="button__login" >Log In</a>
</div>

The same button needs to be added to the corresponding mobile template.

templates/navigation/mobile/mobile_nav_bar_buttons.html
<div class="nav-bar__buttons">
<a href="{{ url_for('signup') }}" class="button__sign-up">Sign Up</a> <!-- 👈 new code -->
<a href="{{ url_for('login') }}" class="button__login" >Log In</a>
</div>

And you should be able to see the Sign Up button on your navigation bar alongside the Log In button.

Main page with sign up button on the navigation bar

Create the Log Out Button

It's time to let your users log out and exit their sessions.

Update the logout route

Similarly to the previous steps, you need to update the logout route.

webapp/routes.py
from urllib.parse import quote_plus, urlencode # 👈 new code
from fastapi import APIRouter, Request
# ... existing code
@auth_router.get("/logout")
def logout(request: Request):
"""
Redirects the user to the Auth0 Universal Login (https://auth0.com/docs/authenticate/login/auth0-universal-login)
"""
# 👇 new code
response = RedirectResponse(
url="https://" + auth0_config['DOMAIN']
+ "/v2/logout?"
+ urlencode(
{
"returnTo": request.url_for("home"),
"client_id": auth0_config['CLIENT_ID'],
},
quote_via=quote_plus,
)
)
request.session.clear()
# 👆 new code
return response # 👈 updated code
# ... existing code

The logout route code is somewhat different from login and signup as it doesn't depend on authlib, but rather it cleans the session to log out the user from the FastAPI application and it performs a redirect to Auth0 to log out the user from the authorization server side as well.

If you don't perform the Auth0 log out, and the user presses the "Log In" button again, it won't be necessary to provide any credentials, and the authentication flow will continue automatically with the Auth0 current authenticated user.

Add the logout button to the template

Open the templates/navigation/desktop/nav_bar_buttons.html and add a new button:

templates/navigation/desktop/nav_bar_buttons.html
<div class="nav-bar__buttons">
<a href="{{ url_for('logout') }}" class="button__logout">Log Out</a> <!-- 👈 new code -->
<a href="{{ url_for('signup') }}" class="button__sign-up">Sign Up</a>
<a href="{{ url_for('login') }}" class="button__login" >Log In</a>
</div>

The same button needs to be added to the mobile template.

templates/navigation/mobile/mobile_nav_bar_buttons.html
<div class="nav-bar__buttons">
<a href="{{ url_for('logout') }}" class="button__logout">Log Out</a> <!-- 👈 new code -->
<a href="{{ url_for('signup') }}" class="button__sign-up">Sign Up</a>
<a href="{{ url_for('login') }}" class="button__login" >Log In</a>
</div>

With the current changes your application should look something like the image below no matter the authentication status of the user.

fastapi sample app with all login logout signup buttons being displayed on the navbar

Now you will focus on providing a better experience for your users by conditionally hiding some of the navigation bar buttons depending on the user's authentication status.

Render Content Based on Authentication

Because you are storing the user's ID token in the session, you'll use this value to decide whether a user is authenticated or not.

Render authentication buttons conditionally

Update the desktop template to conditionally render the buttons depending on the authentication status.

templates/navigation/desktop/nav_bar_buttons.html
<div class="nav-bar__buttons">
{% if request.session %} <!-- 👈 new code -->
<a href="{{ url_for('logout') }}" class="button__logout">Log Out</a>
{% else %} <!-- 👈 new code -->
<a href="{{ url_for('login') }}" class="button__login" >Log In</a>
<a href="{{ url_for('signup') }}" class="button__sign-up" >Sign Up</a>
{% endif %} <!-- 👈 new code -->
</div>

You should apply these changes to the corresponding mobile template as well.

templates/navigation/mobile/mobile_nav_bar_buttons.html
<div class="nav-bar__buttons">
{% if request.session %} <!-- 👈 new code -->
<a href="{{ url_for('logout') }}" class="button__logout">Log Out</a>
{% else %} <!-- 👈 new code -->
<a href="{{ url_for('login') }}" class="button__login" >Log In</a>
<a href="{{ url_for('signup') }}" class="button__sign-up" >Sign Up</a>
{% endif %} <!-- 👈 new code -->
</div>

Render navigation tabs conditionally

Similarly, only authenticated users are allowed to access the Protected and Admin routes, so you should hide the tabs from the navigation bar appropriately.

templates/navigation/desktop/nav_bar_tabs.html
{% macro tab(label='', path='') %}
<a
href="{{ path }}"
class="nav-bar__tab {% if request.url == path %}nav-bar__tab--active{% endif %}"
>
{{ label }}
</a>
{% endmacro %}
<div class="nav-bar__tabs">
{{ tab(label="Profile", path=url_for('profile')) }}
{{ tab(label="Public", path=url_for('public')) }}
{% if request.session %} <!-- 👈 new code -->
{{ tab(label="Protected", path=url_for('protected')) }}
{{ tab(label="Admin", path=url_for('admin')) }}
{% endif %} <!-- 👈 new code -->
</div>

You should also apply these changes to the corresponding mobile template.

templates/navigation/mobile/mobile_nav_bar_tabs.html
{% macro tab(label='', path='') %}
<a
href="{{ path }}"
class="nav-bar__tab {% if request.url == path %}nav-bar__tab--active{% endif %}"
>
{{ label }}
</a>
{% endmacro %}
<div class="nav-bar__tabs">
{{ tab(label="Profile", path=url_for('profile')) }}
{{ tab(label="Public", path=url_for('public')) }}
{% if request.session %} <!-- 👈 new code -->
{{ tab(label="Protected", path=url_for('protected')) }}
{{ tab(label="Admin", path=url_for('admin')) }}
{% endif %} <!-- 👈 new code -->
</div>

Then your application should look like this when the user is not authenticated yet:

fastapi sample app with buttons being displayed on the navbar for a logged out user

Similarly, an authenticated user would see this main page:

Main page with buttons and tabs on the navigation bar being conditionally rendered for authenticated users

Secure your FastAPI routes

Now that the navigation tabs and buttons will render conditionally, you'll need to make sure that only authenticated users have access to certain routes.

Follow the steps in the next sections to the protect the routes from unwanted access.

Create the protection dependency

Create the dependencies.py file inside the auth/ directory like so:

COMMAND
touch auth/dependencies.py

Next update the auth/dependencies.py file to contain the following:

auth/dependencies.py
from fastapi import Request, HTTPException, status
def protected_endpoint(request: Request):
"""
This Dependency protects an endpoint and it can only be accessed if the user has an active session
"""
if 'id_token' not in request.session: # it could be userinfo instead of id_token
# this will redirect people to the login after if they are not logged in
raise HTTPException(
status_code=status.HTTP_307_TEMPORARY_REDIRECT,
detail="Not authorized",
headers={
"Location": "/login"
}
)

This protection guard is responsible for redirecting non-authenticated users to the login page if they try to access protected pages directly.

Secure the profile, protected and admin routes

To protect your endpoints, you'll use the dependency that validates the user session, and if no session is found it will redirect the users automatically to the login route. If, on the contrary, there's an existing session you'll continue with the default method.

Now, simply adjusting a few lines of code you'll require a user to be authenticated to access a given protected route.

webapp/routes.py
from fastapi import APIRouter, Depends, Request # 👈 updated code
from auth.dependencies import protected_endpoint # 👈 new code
from config import templates
from services.message_service import MessageService
webapp_router = APIRouter()
# ... existing code
@webapp_router.get("/profile", dependencies=[Depends(protected_endpoint)]) # 👈 updated code
def profile(request: Request):
# ... existing code
@webapp_router.get("/protected", dependencies=[Depends(protected_endpoint)]) # 👈 updated code
def protected(request: Request):
# ... existing code
@webapp_router.get("/admin", dependencies=[Depends(protected_endpoint)]) # 👈 updated code
def admin(request: Request):
# ... existing code

To see the new behavior in action remember to logout and then try to access a protected page such as /protected. You should be redirected to the login page instead.

Retrieve User Profile Information

After a user successfully logs in, Auth0 sends an ID token to your FastAPI web application. Authentication systems, such as Auth0, use ID Tokens in token-based authentication to cache user profile information and provide it to a client application. The caching of ID tokens can improve the performance and responsiveness of your FastAPI application.

Because you are storing the tokens and information processed by authlib during the authentication routine, you have direct access to the user information from Auth0 in the request.session object.

You can now process that information in the profile route to build the template that shows the user information on the screen with actual user data instead of the mock data present on starter app.

webapp/routes.py
# ... existing code
@webapp_router.get("/profile", dependencies=[Depends(ProtectedEndpoint)])
def profile(request: Request):
"""
Profile endpoint, should only be accessible after login
"""
# Remove mock data
return templates.TemplateResponse(
"profile.html",
{
"request": request,
"userinfo": request.session['userinfo'] # 👈 updated code
}
)
# ... existing code

And now your user should see something like this on their profile page after logging in:

fastapi sample app profile page after login showing the user information obtained from the ID token
You'll notice that the profile page information changed slightly here. Feel free to update that page as you wish. Remember to log in to see the profile page.

Integrate Your FastAPI Application with a Protected API Server

This section focuses on showing you how to get an access token in your FastAPI Web App and how to use it to make API calls to protected API endpoints.

When you use Auth0, you delegate the authentication process to a centralized service. Auth0 provides you with functionality to log in and log out users from your FastAPI application. However, your application may need to access protected resources from another API.

You can also protect an API with Auth0. There are multiple API quickstarts to help you integrate Auth0 with your backend platform.

When you use Auth0 to protect your API, you also delegate the authorization process to a centralized service that ensures only approved client applications can access protected resources on behalf of a user.

Retrieve an Access Token from Auth0

Thanks to authlib there isn't a lot you need to do as you are already storing the information processed by the library, which not only contains user information, the ID Token, but also the access token needed by the application.

To retrieve the access token when you are storing it in a session you can simply do:

access_token = request.session['access_token']

How can you make secure API calls from a FastAPI Web App?

Instead of creating an API from scratch to test the authentication and authorization flow between the client and the server, you can pair this FastAPI web client application with an API server that matches the technology stack you use at work. The FastAPI "Hello World" sample application that you have been building up can interact with any of the "Hello World" API server samples from the Auth0 Developer Center.

Pick an API code sample in your preferred backend framework and language from the list below and follow the instructions on the code sample page to set it up. Once you complete the sample API server setup, please return to this page to learn how to integrate that API server with your FastAPI application.

There are Django, Flask, and FastAPI API samples in the list below should you prefer to stick to Python options.
actix-web
rust
Actix Web/Rust API:Authorization Code Sample
Code sample of a simple Actix Web server that implements token-based authorization using Auth0.
aspnet-core
csharp
ASP.NET Core Code Sample:Web API Authorization
Code sample of a simple ASP.NET Core server that implements token-based authorization using Auth0.
aspnet-core
csharp
ASP.NET Core v5 Code Sample:Web API Authorization
Code sample of a simple ASP.NET Core v5.0 server that implements token-based authorization using Auth0.
django
python
Django/Python API:Authorization Code Sample
Code sample of a simple Django server that implements token-based authorization using Auth0.
express
javascript
Express.js Code Sample:Basic API Authorization
Code sample of a simple Express.js server that implements token-based authorization using Auth0.
express
typescript
Express.js/TypeScript Code Sample:Basic API Authorization
Code sample of a simple Express.js server built with TypeScript that implements token-based authorization using Auth0.
fastapi
python
FastAPI/Python Code Sample:Basic API Authorization
Code sample of a simple FastAPI server that implements token-based authorization using Auth0.
flask
python
Flask/Python API:Authorization Code Sample
Code sample of a simple Flask server that implements token-based authorization using Auth0.
laravel
php
Laravel/PHP Code Sample:Basic API Authorization with Auth0 Laravel SDK
Code sample of a simple Laravel server that implements token-based authorization using the Auth0 Laravel SDK.
laravel
php
Laravel/PHP Code Sample:Basic API Authorization with Auth0 PHP SDK
Code sample of a simple Laravel server that implements token-based authorization using the Auth0 PHP SDK.
lumen
php
Lumen Code Sample:Basic API Authorization
Code sample of a simple Lumen server that implements token-based authorization using Auth0.
nestjs
typescript
NestJS Code Sample:Basic API Authorization
Code sample of a simple NestJS server that implements token-based authorization using Auth0.
phoenix
elixir
Phoenix/Elixir API:Authorization Code Sample
Code sample of a simple Phoenix server that implements token-based authorization using Auth0.
rails
ruby
Ruby on Rails API:Authorization Code Sample
Code sample of a simple Rails server that implements authorization using Auth0.
spring
java
Spring Code Sample:Basic API Authorization
Java code sample that implements token-based authorization in a Spring Web API server to protect API endpoints, using Spring Security and the Okta Spring Boot Starter.
spring
java
Spring Functional Code Sample:Basic API Authorization
Java code sample that implements token-based authorization in a Spring Web API server to protect API endpoints, following a functional approach.
spring-webflux
java
Spring WebFlux Code Sample:Basic API Authorization
Java code sample that implements token-based authorization in a Spring WebFlux API server to protect API endpoints, using Spring Security and the Okta Spring Boot Starter.
standard-library
golang
Golang Code Sample:Basic API Authorization
Code sample of a simple Golang server that implements token-based authorization using Auth0.
symfony
php
Symfony Code Sample:Basic API Authorization
Code sample of a simple Symfony server that implements token-based authorization using Auth0.

Call a protected API from you FastAPI Web App

Add external API Variables

Now that you setup your API, you should have an Auth0 Audience. Store the audience in the field bellow:

Then adjust your .config file in your FastAPI web application accordingly:

.config
[AUTH0]
# ... existing code
# 👇 new code
[API]
AUTH0_AUDIENCE=AUTH0-AUDIENCE
API_SERVER_URL=http://localhost:6060
# 👆 new code
[WEBAPP]
# ... existing code
Remember to restart the server if it is still running after you make the .config change so the new variables can be properly loaded in.

You are using AUTH0_AUDIENCE to add the value of your Auth0 API Audience so that your FastAPI web application can request resources from the API represented by that value.

What AUTH0_AUDIENCE and API_SERVER_URL represent?

The API_SERVER_URL is the URL where your sample API server listens for requests. In production, you'll change this value to the URL of your live server.

Your FastAPI application must pass an access token when it calls a target API to access protected resources. You can request an access token in a format that the API can verify by passing the audience during the authentication request in your login and signup routes.

The value of the Auth0 Audience must be the same for both the FastAPI web client application and the API server you decided to set up.

Why is the Auth0 Audience value the same for both apps? Auth0 uses the value of the audience property to determine which resource server (API) the user is authorizing your FastAPI application to access. It's like a phone number. You want to ensure that your FastAPI application "texts the right API".

Now, add the audience login. You'll need to make two updates, first to the login endpoint, this will make Auth0 return the access token:

auth/routes.py
# ... existing code
from auth.config import auth0_config, oauth
from config import config # 👈 new code
# ... existing code
@auth_router.get("/login")
async def login(request: Request):
# ... existing code
if 'id_token' not in request.session:
return await oauth.auth0.authorize_redirect(
request,
redirect_uri=request.url_for("callback"), # 👈 updated code
audience=config['API']['AUTH0_AUDIENCE'] # 👈 new code
)
return RedirectResponse(url=request.url_for("profile"))
# ... existing code

Now make the change also to the callback function so you can store the received access token in the session like so:

auth/routes.py
# ... existing code
@auth_router.get("/callback")
async def callback(request: Request):
"""
Callback redirect from Auth0
"""
token = await oauth.auth0.authorize_access_token(request)
# Store `access_token`, `id_token`, and `userinfo` in session # 👈 updated code
request.session['access_token'] = token['access_token'] # 👈 new code
request.session['id_token'] = token['id_token']
request.session['userinfo'] = token['userinfo']
return RedirectResponse(url=request.url_for("profile"))

This change will show a new consent page to the users once they login again as the user now need to give your web app permissions to make requests in the users name.

Update the Message Service to call an external API

Until now, the message service had hardcoed values to emulate the API response for public, protected, and admin pages.

Now you'll update the service to communicate to external API. Update the services/message_service.py as follows:

services/message_service.py
import httpx # 👈 new code
from typing import TypedDict
from config import config # 👈 new code
class MessageServiceType(TypedDict):
text: str
# 👇 new code
def make_request(path, access_token=None):
url = f"{config['API']['API_SERVER_URL']}{path}"
if access_token is None:
headers = {}
else:
headers = {
'authorization': 'Bearer {}'.format(access_token)
}
r = httpx.get(url, headers=headers)
return r.json()
# 👆 new code
class MessageService:
def public_message(self) -> MessageServiceType:
return {
'text': 'This is a public message.'
}
# 👇 updated code
def protected_message(self, access_token):
return make_request('/api/messages/protected', access_token)
def admin_message(self, access_token):
return make_request('/api/messages/admin', access_token)
# 👆 updated code

Since your changes now affect the signature of the functions admin_message and protected_message to expect a parameter access_token you need to provide it during their usage. Update it as follows:

webapp/routes.py
# ... existing code
@webapp_router.get("/protected", dependencies=[Depends(protected_endpoint)])
def protected(request: Request):
"""
Protected endpoint, should only be accessible after login
"""
access_token = request.session['access_token'] # 👈 new code
return templates.TemplateResponse(
"protected.html",
{
"request": request,
"message": MessageService().protected_message(access_token) # 👈 updated code
}
)
@webapp_router.get("/admin", dependencies=[Depends(protected_endpoint)])
def admin(request: Request):
"""
Admin endpoint, should only be accessible after login if the user has the admin permissions
"""
access_token = request.session['access_token'] # 👈 new code
return templates.TemplateResponse(
"admin.html",
{
"request": request,
"message": MessageService().admin_message(access_token) # 👈 new code
}
)
Getting an error on the admin or protected pages? Try logging out and logging in. Your user should request new access for the external API call.

You are getting the access token from the session, which you previously stored after the user authenticated in the callback method. When the MessageService pass a None as the value for the access_token to the API and you'll get a { message: Permission Denied } error from the External API.

Conclusion

You learned to implemented user authentication in FastAPI to identify your users, get user profile information, and control the content that your users can access by protecting routes and API resources.

This guide covered the most common authentication use case for a FastAPI Web application: simple login and logout. However, Auth0 is an extensible and flexible identity platform that can help you achieve even more. If you have a more complex use case, check out the Auth0 Architecture Scenarios to learn more about the typical architecture scenarios we have identified when working with customers on implementing Auth0.

We'll cover advanced authentication patterns and tooling in future guides, such as using a pop-up instead of redirecting users to log in, adding permissions to ID tokens, using metadata to enhance user profiles, and much more.

Click the GitHub button below to see the complete code you just built.