Skip to content

Flask

Flask is probably the most popular Python web framework.

The Fief Python client provides tools to help you integrate Fief authentication in your Flask project. Let's see how to use them!

Install the client

Install the Fief client with the optional Flask dependencies:

pip install "fief-client[flask]"

API example

This is for you if...

  • Your Flask backend will work as a pure REST API.
  • You have a separate frontend, like a JavaScript or mobile app, that'll take care of the OAuth2 flow.

In this first example, we won't implement routes to perform the OAuth2 authentication. The goal here is just to show you how to protect your API route with a Fief access token.

app.py
from fief_client import Fief
from fief_client.integrations.flask import (
    FiefAuth,
    FiefAuthForbidden,
    FiefAuthUnauthorized,
    get_authorization_scheme_token,
)
from flask import Flask, g

fief = Fief(  # (1)!
    "https://fief.mydomain.com",
    "YOUR_CLIENT_ID",
    "YOUR_CLIENT_SECRET",
)

auth = FiefAuth(fief, get_authorization_scheme_token())  # (2)!

app = Flask(__name__)


@app.errorhandler(FiefAuthUnauthorized)  # (3)!
def fief_unauthorized_error(e):
    return "", 401


@app.errorhandler(FiefAuthForbidden)  # (4)!
def fief_forbidden_error(e):
    return "", 403


@app.get("/authenticated")
@auth.authenticated()  # (5)!
def get_authenticated():
    return g.access_token_info  # (6)!


@app.get("/authenticated-scope")
@auth.authenticated(scope=["openid", "required_scope"])  # (7)!
def get_authenticated_scope():
    return g.access_token_info


@app.get("/authenticated-permissions")
@auth.authenticated(permissions=["castles:read"])  # (8)!
def get_authenticated_permissions():
    return g.access_token_info
  1. Fief client instantiation

    As we showed in the standard Python section, we instantiate here a Fief client here with the base tenant URL and client credentials.

  2. Fief helper for Flask

    This is the helper doing the tedious work for you with Flask. It first needs an instance of the Fief client we created above and a function retrieving the access token from the Flask request.

    It's a simple function which can use the global request object to retrieve the access token.

    For convenience, we provide two of them: get_authorization_scheme_token and get_cookie.

  3. Error handler for FiefAuthUnauthorized

    When a protected route is called without a valid access token, the Fief helper will raise the FiefAuthUnauthorized.

    By registering a Flask error handler, we can catch this error and customize the response returned to the user. Here, we just return an empty response with the 401 status code.

  4. Error handler for FiefAuthForbidden

    When a request is made with a valid access token but without the required scope, the Fief helper will raise the FiefAuthForbidden.

    By registering a Flask error handler, we can catch this error and customize the response returned to the user. Here, we just return an empty response with the 403 status code.

  5. authenticated decorator

    This is where the magic happens: FiefAuth gives you a authenticated decorator to check for the access token and optionally for required scopes.

    If everything goes well, the route logic will be executed.

  6. access_token_info dictionary is available in g

    When a valid access token is found in the request, the access_token_info decorator will automatically add the access_token_info property to the global g application context of Flask.

    This access_token_info property is a FiefAccessTokenInfo dictionary containing the ID of the user, the list of allowed scopes and permissions and the raw access token.

  7. Check for scopes

    The access_token_info decorator accepts an optional scope argument where you can list the scope required to access this route.

    If the access token doesn't have the required scope, FiefAuthForbidden error is raised.

  8. Check for permissions

    The access_token_info decorator accepts an optional permissions argument where you can list the permissions required to access this route.

    If the user doesn't have the required permissions, FiefAuthForbidden is raised.

And that's about it!

Optional user

Sometimes, you need to have a route retrieving the user if there is one authenticated, but still working if there none. To do this, you can leverage the optional parameter of the authenticated decorator.

app.py
from fief_client import Fief
from fief_client.integrations.flask import (
    FiefAuth,
    FiefAuthForbidden,
    FiefAuthUnauthorized,
    get_authorization_scheme_token,
)
from flask import Flask, g

fief = Fief(
    "https://fief.mydomain.com",
    "YOUR_CLIENT_ID",
    "YOUR_CLIENT_SECRET",
)

auth = FiefAuth(fief, get_authorization_scheme_token())

app = Flask(__name__)


@app.errorhandler(FiefAuthUnauthorized)
def fief_unauthorized_error(e):
    return "", 401


@app.errorhandler(FiefAuthForbidden)
def fief_forbidden_error(e):
    return "", 403


@app.get("/authenticated")
@auth.authenticated(optional=True)
def get_authenticated():
    if g.access_token_info is None:
        return {"message": "Anonymous user"}
    return g.access_token_info

Web application example

This is for you if...

  • Your Flask backend will render HTML pages.
  • Your application is intended to be used in a browser.

Prerequisites

  • Allow the following Redirect URI on your Fief Client: http://localhost:8000/auth-callback

The examples we showed previously are working well in a pure REST API context: a frontend, like interactive documentation, a JavaScript application or a mobile app will take care of the OAuth2 authentication flow to retrieve an access token before making request to your API.

Another common context is traditional web application, where the server takes care of generating HTML pages before returning it to the browser. In this case, we'll need some routes to redirect the user to the Fief login page if they're not authenticated and take care of storing the access token somewhere. This is what'll show in this example.

Besides, we'll usually need the basic information about the authenticated user, like its email or the values of the custom user fields. We'll see how we can use it.

Basically, here's what we'll do:

  1. This time, we'll expect the access token to be passed through a traditional cookie instead of an HTTP header. Cookies are very convenient when designing web apps because they are handled automatically by the browser.
  2. If the cookie is not present, we'll redirect the user to the Fief login page. Once again, the browser will help us a lot here since it'll automatically follow the redirection.
  3. Upon successful login, Fief will automatically redirect the user to the callback route. This callback route will take care of setting a new cookie containing the access token. It means that the access token will be safely stored in the browser memory.
  4. Finally, the user is redirected back to the protected route. The browser will automatically send the cookie containing the access token: our request is now authenticated!
app.py
import uuid
from typing import Optional

from fief_client import Fief, FiefUserInfo
from fief_client.integrations.flask import (
    FiefAuth,
    FiefAuthForbidden,
    FiefAuthUnauthorized,
    get_cookie,
)
from flask import Flask, g, redirect, request, session, url_for

SESSION_COOKIE_NAME = "user_session"
SECRET_KEY = "SECRET"  # (1)!


fief = Fief(  # (2)!
    "https://fief.mydomain.com",
    "YOUR_CLIENT_ID",
    "YOUR_CLIENT_SECRET",
)


def get_userinfo_cache(id: uuid.UUID) -> Optional[FiefUserInfo]:  # (3)!
    return session.get(f"userinfo-{str(id)}")


def set_userinfo_cache(id: uuid.UUID, userinfo: FiefUserInfo) -> None:  # (4)!
    session[f"userinfo-{str(id)}"] = userinfo


auth = FiefAuth(
    fief,
    get_cookie(SESSION_COOKIE_NAME),  # (5)
    get_userinfo_cache=get_userinfo_cache,  # (6)!
    set_userinfo_cache=set_userinfo_cache,
)
app = Flask(__name__)
app.secret_key = SECRET_KEY


@app.errorhandler(FiefAuthUnauthorized)  # (7)!
def fief_unauthorized_error(e):
    redirect_uri = url_for("auth_callback", _external=True)  # (8)!
    auth_url = fief.auth_url(redirect_uri, scope=["openid"])  # (9)!
    return redirect(auth_url)  # (10)!


@app.errorhandler(FiefAuthForbidden)
def fief_forbidden_error(e):
    return "", 403


@app.get("/auth-callback")  # (11)!
def auth_callback():
    redirect_uri = url_for("auth_callback", _external=True)
    code = request.args["code"]
    tokens, userinfo = fief.auth_callback(code, redirect_uri)  # (12)!

    response = redirect(url_for("protected"))  # (13)!
    response.set_cookie(  # (14)!
        SESSION_COOKIE_NAME,
        tokens["access_token"],
        max_age=tokens["expires_in"],
        httponly=True,  # (15)!
        secure=False,  # ❌ Set this to `True` in production (16)!
    )

    set_userinfo_cache(uuid.UUID(userinfo["sub"]), userinfo)  # (17)!

    return response


@app.get("/protected")
@auth.current_user()  # (18)!
def protected():
    user = g.user  # (19)!
    return f"<h1>You are authenticated. Your user email is {user['email']}</h1>"
  1. Define a secret key for Flask

    We'll use the Sessions mechanism from Flask to keep user information in cache. To enable it, we need to set a secret key for Flask.

    Generate a strong passphrase and don't share it.

    Avoid to hardcode your secrets in your code

    It's usually not recommended to hardcode secrets like Client ID and Secret in your code like this. If your code gets published on the web, for example on GitHub, the security of your instance would be compromised.

    Besides, it'll be harder if you need to deploy on several environments, like a staging or testing one, in addition to your production environment.

    A standard and widely-used approach is to use environment variables.

  2. This doesn't change from the previous example

    The Fief client is always at the heart of the integration 😉

  3. We define a function to retrieve user information from cache

    To make sure we don't call the Fief API every time we want the user data, we'll cache it in our application. It'll be way more performant!

    To do this, we implement a simple function allowing us to retrieve the user information given a user ID.

    In this example, we simply use the Sessions mechanism from Flask, but it can be something more complex, like reading from a Redis store.

  4. We define a function to set user information in cache

    As you probably have guessed, we need the other side of the operation: saving user information in cache.

    To do this, we implement a simple function accepting a user ID and a FiefUserInfo dictionary as arguments. There, you'll need to store this data in cache.

    In this example, we simply use the Sessions mechanism from Flask, but it can be something more complex, like writing to a Redis store.

  5. We use a cookie getter

    Contrary to the previous examples, we expect the access token to be passed in a cookie. Thus, we use the get_cookie getter.

    Notice that we set the name of this cookie through the SESSION_COOKIE_NAME constant.

  6. We pass both get_userinfo_cache and set_userinfo_cache as arguments

    Basically, we tell FiefAuth to use the caching functions we implemented when we want to get the user information.

    That's why it's important to strictly follow the functions signature presented above: FiefAuth will call them inside its logic.

  7. We change the error handler for FiefAuthUnauthorized

    This time, we'll generate a redirect response so the user can login on Fief.

  8. We build the redirect URL

    This points to our /auth-callback route that we define below.

  9. We generate an authorization URL on the Fief server

    Thanks to the auth_url method on the Fief client, we can automatically generate the authorization URL on the Fief server.

  10. We build a redirect response

    We redirect the user to the Fief authorization URL.

  11. We implement an /auth-callback route

    This is the route that'll take care of exchanging the authorization code with a fresh access token and save it in a cookie.

  12. We generate an access token

    We finish the OAuth2 flow by exchanging the authorization code with a fresh access token.

  13. We build a redirection to the /protected route

    The user will now be correctly authenticated to our web application. Thus, we can redirect them to a protected page.

  14. We build a new cookie containing the access token

    The response will contain a Set-Cookie header instructing the browser to save the access token in its memory. This method allows us to configure each properties of the cookie.

    You can read more about HTTP cookies on the MDN documentation.

  15. Set the cookie as HTTPOnly

    For such sensitive values, it's strongly recommended to set the cookie as HTTPOnly. It means that it won't be possible to read its value from JavaScript, reducing potential attacks.

  16. Set the cookie as secure in production

    For such sensitive values, it's strongly recommended to set the cookie as Secure. It tells the browser to send the cookie only on HTTPS (SSL) connection, reducing the risk of the access token to be stolen by a attacker between the client and the server.

    However, in a local environment, you usually don't serve your application with SSL. That's why we set it to False in this example. A common approach to handle this is to have an environment variable to control this parameter, so you can disable it in local and enable it in production.

  17. We cache the user information

    When a user has successfully authenticated, we do not only get the access token: we also get an ID token which already contains the user information.

    Hence, we'll take this opportunity to store it in our cache! The ID token is automatically decoded by fief.auth_callback method.

    Thus, we just have to use our cache function to store it!

  18. Use the current_user decorator

    This time, we use the current_user decorator instead of authenticated. Under the hood, it'll stil call authenticated and check if the cookie is available in the request and proceed if everything goes well. However, it'll return you a FiefUserInfo dictionary containing the data of the user.

    If the request is not authenticated, an FiefAuthUnauthorized error will be raised and the user will be redirected to the Fief login page.

  19. user dictionary is available in g

    If the request is properly authenticated, the current_user decorator will automatically add the user property to the global g application context of Flask.

    This user property is a FiefUserInfo dictionary containing the user data. If it's not available in cache, it's automatically retrieved from the Fief API.

That's it! If you run this application and go to http://localhost:8000/protected, you'll be redirected to the Fief login page and experience the authentication flow before getting back to this route with a proper authentication cookie.

current_user can also check for scope and permissions

In a similar way as we shown in the API example, you can also require the access token to be granted a list of scopes or the user to be granted a list of permissions.

@app.get("/protected")
@auth.current_user(scope=["openid", "required_scope"])
def protected():
    ...
@app.get("/protected")
@auth.current_user(permissions=["castles:read"])
def protected():
    ...

You can also optionally require the user

In a similar way as we shown in the API example, you can leverage the optional parameter to make the route works even if no user is authenticated.

@app.get("/protected")
@auth.current_user(optional=True)
def protected():
    user = g.user
    if user is None:
        return f"<h1>You are an anonymous user.</h1>"
    return f"<h1>You are authenticated. Your user email is {user['email']}</h1>"