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:
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:
While a web request with an Authorization
header will produce a response with
the following body:
For example, to generate web requests using curl
:
Gets the output: {"anonymous":true}
.
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:
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