Skip to content

Advanced usage

Cookies

By default LoginManager expects the token to be in the Authorization header. However cookie support can be enabled.

from fastapi_login import LoginManager

manager = LoginManager("secret", "/login", use_cookie=True)

For convenince a set_cookie method is provided, which sets the cookie, using LoginManager.cookie_name and the recommended HTTPOnly flag.

@app.post("/login")
def login(response: Response):
    ...
    token = manager.create_access_token(data=dict(sub=user.email))
    manager.set_cookie(response, token)
    return response

Configuration

By default, LoginManager looks for a cookie with the name access-token, this can be changed using the cookie_name argument, when creating the instance.

manager = LoginManager(
    ...,
    cookie_name='custom-cookie-name'
)

If you only want to support authorization using cookies, use_header can be set to false on initialization.

from fastapi_login import LoginManager

manager = LoginManager("secret", "/login", use_cookie=True, use_header=False)

Exception handling

Sometimes it is needed to run some code if a user is not authenticated, this can achieved, by setting a custom Exception on the LoginManager instance.

from fastapi import Request
from starlette.responses import RedirectResponse

from fastapi_login import LoginManager


class NotAuthenticatedException(Exception):
    pass


# Before version 1.7.0
# manager.not_authenticated_exception = NotAuthenticatedException

# Before version 1.10.0
# manager = LoginManager(..., custom_exception=NotAuthenticatedException)

# The updated way
manager = LoginManager(..., not_authenticated_exception=NotAuthenticatedException)


@app.exception_handler(NotAuthenticatedException)
def auth_exception_handler(request: Request, exc: NotAuthenticatedException):
    """
    Redirect the user to the login page if not logged in
    """
    return RedirectResponse(url="/login")

Now whenever there is no, or an invalid token present in the request, your exception will be raised, and the exception handler will be executed.

More to writing exception handlers can be found in the official documentation of FastAPI

Token expiry

By default token's expire after 15 minutes. This can be changed using the expires argument in the create_access_token method.

# expires after 12 hours
long_token = manager.create_access_token(data=data, expires=timedelta(hours=12))

If you want to change the expiry for every token issued the default expiry can be set on initialization

manager = LoginManager(
    ...,
    default_expiry=timedelta(hours=12)
)

Middleware

Optionally a LoginManager instance can also be added as a middleware. It's important to note that request.state.user is set to None if no (valid) token is present in the request.

from fastapi.requests import Request

manager.attach_middleware(app)


@app.route("/showcase")
def showcase(request: Request):
    # None if unauthorized
    user = request.state.user

Using the middleware it's easy to write your own dependencies, that have access to your user object.

from fastapi.exceptions import HTTPException
from fastapi.requests import Request


def is_admin(request: Request):
    user = request.state.user
    if user is None:
        raise HTTPException(401)
    # assuming our user object has a is_admin property
    elif not user.is_admin:
        raise HTTPException(401)
    else:
        return user

OAuth2 scopes

In addition to normal token authentication, OAuth2 scopes can be used to restrict access to certain routes.

@app.get("/scoped/route")
def scoped_route(user=Security(manager, scopes=["required", "scopes", "here"])):
    ...

Notice how instead of the normally used fastapi.Depends fastapi.Security is used. In order to give your token the required scopes LoginManager.create_access_token has a scopes parameter. In order for the scopes to show up in the OpenAPI docs, your scopes need to be passed as an argument when instantiating LoginManager.

Predefining additional user_loader arguments

The LoginManager.user_loader can also take arguments which will be passed on the callback

@manager.user_loader(db_session=...)
def query_user(user_id: str, db_session):
    """
    Get a user from the db
    :param user_id: E-Mail of the user
    :param db_session: The currently active connection to the database
    :return: None or the user object
    """
    return db_session.get(user_id)

Callable as extra argument

Before version 1.7.0 empty parentheses were not needed after the decorator. To provide backwards compatibility it's still possible to omit them as of v1.7.1. Because of this it's however not possible to pass a callable object as the first extra argument.

This will not work:

def some_callable_object(...):
    ...

@manager.user_loader(some_callable_object)
def load_user(email, some_callable):
    ...
If you need this functionality you need to use keyword arguments:
@manager.user_loader(some_callable=some_callable_object)
def load_user(email, some_callable)

Asymmetric algorithms

Thanks to filwaline in addition to symmetric keys, RSA can also be used to sign the tokens.

Required dependencies

The cryptography packages is required for this. Run the following command to install all the required dependencies.

pip install fastapi-login[asymmetric]

Supported algorithms

Currently RS256 is the only asymmetric algorithm supported by the package. If you need another algorithm, please open an issue and I will consider adding it.

To use the asymmetric algorithm choose algorithm="RS256" when initiating LoginManager.

LoginManager(
    secret="...", token_url="...",
    algorithm="RS256"
)

If your private key is not password protected the usage of the package stays exactly the same.

Hint

You don't need to pass your public key explicitly, as it can be derived from the private key.

However, if you used a password during the key generation the syntax changes slightly.

manager = LoginManager(
    secret={"private_key": "your_rsa_key", "password": "your_password_for_the_key"},
    token_url="...",
    algorithm="RS256",
)

Note how instead of just using the key, we now have to pass a dictionary with the private_key and the password fields set.