Skip to content

Authentication in BlackSheep

The term 'authentication strategy' in the context of a web application refers to the process of identifying the user accessing the application. BlackSheep provides a built-in authentication strategy for request handlers. This page covers:

  • How to use the built-in authentication strategy.
  • How to configure a custom authentication handler.
  • How to use the built-in support for JWT Bearer authentication.
  • How to read the user's context in request handlers.

Additional dependencies.

Using JWT Bearer and OpenID integrations requires additional dependencies. Install them by running: pip install blacksheep[full].

Underlying library

The authentication and authorization logic for BlackSheep is packaged and published in a dedicated library: guardpost (in pypi).

How to use built-in authentication

Common strategies for identifying users in web applications include:

  • Reading an Authorization: Bearer xxx request header containing a JWT. with claims that identify the user.
  • Reading a signed token from a cookie.

The following sections first explain how to use the built-in support for JWT Bearer tokens and then describe how to write a custom authentication handler.

Terms: user, service, principal.

The term 'user' typically refers to human users, while 'service' describes non-human clients. In Java and .NET, the term 'principal' is commonly used to describe a generic identity.

OIDC

BlackSheep implements built-in support for OpenID Connect authentication, meaning that it can be easily integrated with identity provider services such as:

Examples in GitHub.

The Neoteroi/BlackSheep-Examples/ repository in GitHub contains examples of JWT Bearer authentication and OpenID Connect integrations.

A basic example of integration with any of the identity providers listed above, using implicit flow for id_token (which removes the need to handle secrets), is shown below:

from blacksheep import Application, get, html, pretty_json
from blacksheep.server.authentication.oidc import OpenIDSettings, use_openid_connect
from guardpost.authentication import Identity

app = Application()


# basic Auth0 integration that handles only an id_token
use_openid_connect(
    app,
    OpenIDSettings(
        authority="<YOUR_AUTHORITY>",
        client_id="<CLIENT_ID>",
        callback_path="<CALLBACK_PATH>",
    ),
)


@get("/")
async def home(user: Identity):
    if user.is_authenticated():
        response = pretty_json(user.claims)

        return response

    return html("<a href='/sign-in'>Sign in</a><br/>")

Where:

Parameter Description
YOUR_AUTHORITY The URL to your account, like https://neoteroi.eu.auth0.com
CLIENT_ID Your app registration ID
CALLBACK_PATH The path that is enabled for reply_uri in your app settings, for example if you enabled for localhost: http://localhost:5000/authorization-callback, the value should be /authorization-callback

For more information and examples, refer to the dedicated page about OpenID Connect authentication.

JWT Bearer

BlackSheep implements built-in support for JWT Bearer authentication, and validation of JWTs:

  • Issued by identity providers implementing OpenID Connect (OIDC) discovery (such as Auth0, Microsoft Entra ID).
  • And more in general, JWTs signed using asymmetric encryption and verified using public RSA keys.

The following example shows how to configure JWT Bearer authentication for an application registered in Microsoft Entra ID, and also how to configure authorization to restrict access to certain methods, only for users who are successfully authenticated:

from guardpost import Policy, User
from guardpost.common import AuthenticatedRequirement

from blacksheep import Application, get, json
from blacksheep.server.authentication.jwt import JWTBearerAuthentication
from blacksheep.server.authorization import auth

app = Application()

app.use_authentication().add(
    JWTBearerAuthentication(
        authority="https://login.microsoftonline.com/<YOUR_TENANT_NAME>.onmicrosoft.com",
        valid_audiences=["<YOUR_APP_CLIENT_ID>"],
        valid_issuers=["https://login.microsoftonline.com/<YOUR_TENANT_ID>/v2.0"],
    )
)

# configure authorization, to restrict access to methods using @auth decorator
authorization = app.use_authorization()

authorization += Policy("example_name", AuthenticatedRequirement())


@get("/")
def home():
    return "Hello, World"


@auth("example_name")
@get("/api/message")
def example():
    return "This is only for authenticated users"


@get("/open/")
async def open(user: User | None):
    if user is None:
        return json({"anonymous": True})
    else:
        return json(user.claims)

The built-in handler for JWT Bearer authentication does not currently support JWTs signed with symmetric keys. Support for symmetric keys might be added in the future.

💡

It is possible to configure several JWTBearerAuthentication handlers, for applications that need to support more than one identity provider. For example, for applications that need to support sign-in through Auth0, Azure Active Directory, Azure Active Directory B2C.

Writing a custom authentication handler

The example below shows how to configure a custom authentication handler that obtains the user's identity for each web request.

from blacksheep import Application, Request, auth, get, json
from guardpost import AuthenticationHandler, Identity, User


app = Application(show_error_details=True)


class ExampleAuthHandler(AuthenticationHandler):
    def __init__(self):
        pass

    async def authenticate(self, context: Request) -> Identity | None:
        # TODO: apply the desired logic to obtain a user's identity from
        # information in the web request, for example reading a piece of
        # information from a header (or cookie).
        header_value = context.get_first_header(b"Authorization")

        if header_value:
            # implement your logic to obtain the user
            # in this example, an identity is hard-coded just to illustrate
            # testing in the next paragraph
            context.identity = Identity({"name": "Jan Kowalski"}, "MOCK")
        else:
            # if the request cannot be authenticated, set the context.identity
            # to None - do not throw exception because the app might support
            # different ways to authenticate users
            context.identity = None
        return context.identity


app.use_authentication().add(ExampleAuthHandler())


@get("/")
def home():
    return "Hello, World"


@auth("example_name")
@get("/api/message")
def example():
    return "This is only for authenticated users"


@get("/open/")
async def open(user: User | None):
    if user is None:
        return json({"anonymous": True})
    else:
        return json(user.claims)

It is possible to configure several authentication handlers to implement different ways to identify users. To distinguish how the user was authenticated, use the second parameter of the Identity constructor:

identity = Identity({"name": "Jan Kowalski"}, "AUTHENTICATION_MODE")

The authentication context is the Request instance created to handle the incoming web request. Authentication handlers must set the identity property on the request to enable the automatic injection of user via dependency injection.

Testing the example

To test the example above, start a web server as explained in the getting started guide, then navigate to its root. A web request to the root of the application without an Authorization header will produce a response with the following body:

{"anonymous":true}

While a web request with an Authorization header will produce a response with the following body:

{"name":"Jan Kowalski"}

For example, to generate web requests using curl:

curl  http://127.0.0.1:44555/open

Gets the output: {"anonymous":true}.

curl -H "Authorization: foo" http://127.0.0.1:44555/open

Gets the output: {"name":"Jan Kowalski"}.

The application has been started on port 44555 (e.g. uvicorn server:app --port=44555).

Reading a user's context

The example below shows how a user's identity can be read from the web request:

from guardpost.authentication import Identity


@get("/")
async def for_anybody(user: Identity | None):
    ...
@get("/")
async def for_anybody(request: Request):
    user = request.identity
    # user can be None or an instance of Identity (set in the authentication
    # handler)

Next

While authentication focuses on identifying users, authorization determines whether a user is permitted to perform the requested action. The next page describes the built-in authorization strategy in BlackSheep.

Last modified on: 2025-04-22 08:29:25

RP
EW
RV