Skip to content

Authentication in BlackSheep

The words "authentication strategy" in the context of a web application refer to the ability to identify the user who is using the application. BlackSheep implements a built-in authentication strategy for request handlers. This page describes:

  • 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.

Note: the word "user" is usually used only to refer to human users, while the word "service" is used to describe non-human clients. In Java and .NET, a common word to describe a generic client is "principal".

❗ using JWT Bearer and OpenID integrations requires more dependencies: use pip install blacksheep[full] to use these features

Underlying library

The authentication and authorization logic implemented for BlackSheep was packed and published into a dedicated library: guardpost (in pypi).

How to use built-in authentication

Examples of common strategies to identify 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 next paragraphs explain first how to use the built-in support for JWT Bearer tokens, and how to write a custom authentication handler.

OIDC

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

A basic example integration with any of the identity providers above, having implicit flow enabled for id_token (meaning that the code doesn't need to handle any secret), looks like the following:

from blacksheep.server.application import Application
from blacksheep.server.authentication.oidc import OpenIDSettings, use_openid_connect
from blacksheep.server.responses import html, pretty_json
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>",
    ),
)


@app.route("/")
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, Azure Active Directory)
  • 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 Azure Active Directory, and also how to configure authorization to restrict access to certain methods, only for users who are successfully authenticated:

from blacksheep.server import Application
from blacksheep.server.authorization import auth
from guardpost.common import AuthenticatedRequirement, Policy

from blacksheep.server.authentication.jwt import JWTBearerAuthentication


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 = app.router.get


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


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

🚀 New in version 1.2.1

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

💡 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 user's identity for each web request.

from typing import Optional

from blacksheep.messages import Request
from blacksheep.server import Application
from blacksheep.server.responses import json
from guardpost.asynchronous.authentication import AuthenticationHandler, Identity
from guardpost.authentication import User

app = Application(show_error_details=True)
get = app.router.get


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

    async def authenticate(self, context: Request) -> Optional[Identity]:
        # 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 custom header (or cookie).
        header_value = context.get_first_header(b"Example")

        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())

It is possible to configure several authentication handlers to implement different ways to identify users. To differentiate the way the user has been authenticated, use the second parameter of Identity's constructor:

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

The authentication context is the instance of Request created to handle the incoming web request. Authentication handlers must set the identity property on the request, to enable automatic injection of user by 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 {"anonymous":true}

$ curl -H "Authorization: foo" http://127.0.0.1:44555/open {"name":"Jan
Kowalski"}

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

Reading user's context from the request

The example below show how the user's identity can be read from the web request

Directly from the request:

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

Using binders:

from guardpost.authentication import Identity


@get("/")
async def for_anybody(user: Optional[Identity]):
    ...

Next

While authentication deals with identifying users, authorization deals with determining whether the user is authorized to do the action of the web request. The next page describes the built-in authorization strategy in BlackSheep.