Skip to content

Authentication

Starlette provides a simple but powerful interface for handling authentication and permissions. Once you have AuthenticationMiddleware installed with the proper authentication backend, the request.user and request.auth interfaces will be available in your endpoint.

from starlette.applications import Starlette
from starlette.authentication import (
    AuthCredentials, AuthenticationBackend, AuthenticationError, SimpleUser
)
from starlette.middleware import Middleware
from starlette.middleware.authentication import AuthenticationMiddleware
from starlette.responses import PlainTextResponse
from starlette.routing import Route
import base64
import binascii


class BasicAuthBackend(AuthenticationBackend):
    async def authenticate(self, conn):
        if "Authorization" not in conn.headers:
            return

        auth = conn.headers["Authorization"]
        try:
            scheme, credentials = auth.split()
            if scheme.lower() != 'basic':
                return
            decoded = base64.b64decode(credentials).decode("ascii")
        except (ValueError, UnicodeDecodeError, binascii.Error) as exc:
            raise AuthenticationError('Invalid basic auth credentials')

        username, _, password = decoded.partition(":")
        # TODO: You'd want to verify the username and password here.
        return AuthCredentials(["authenticated"]), SimpleUser(username)


async def homepage(request):
    if request.user.is_authenticated:
        return PlainTextResponse('Hello, ' + request.user.display_name)
    return PlainTextResponse('Hello, you')

routes = [
    Route("/", endpoint=homepage)
]

middleware = [
    Middleware(AuthenticationMiddleware, backend=BasicAuthBackend())
]

app = Starlette(routes=routes, middleware=middleware)

user

Once AuthenticationMiddleware is installed, the request.user interface will be available for endpoints or other middleware.

This interface should inherit a subclass of BaseUser that provides both attributes, as well as any other information that your user model contains.

  • .is_authenticated
  • .display_name

Starlette provides two built-in user implementations:UnauthenticatedUser(), and SimpleUser(username).

authentication credentials

It is important that authentication credentials be viewed as a separate concept from the user. Authentication schemes should be able to restrict or grant specific privileges independently of the user's identity.

AuthCredentials class provides request.auth Basic interfaces exposed:

  • .scopes

jurisdiction

Permissions are implemented as endpoint decorators to enforce that incoming requests contain the required authentication scope.

from starlette.authentication import requires


@requires('authenticated')
async def dashboard(request):
    ...

You can include one or more of the ranges you want:

from starlette.authentication import requires


@requires(['authenticated', 'admin'])
async def dashboard(request):
    ...

By default, a 403 response is returned when permission is not granted. In some cases, you may want to customize this, such as hiding information about URL layout from unauthenticated users.

from starlette.authentication import requires


@requires(['authenticated', 'admin'], status_code=404)
async def dashboard(request):
    ...

!!! Note that WebSockets does not support the status_code parameter. For these cases, the 403 (forbidden) status code will always be used.

Or you might want to redirect unauthenticated users to other pages.

from starlette.authentication import requires


async def homepage(request):
    ...


@requires('authenticated', redirect='homepage')
async def dashboard(request):
    ...

When redirecting users, the page you redirect them to includes the URL they originally requested in the next query parameters:

from starlette.authentication import requires
from starlette.responses import RedirectResponse


@requires('authenticated', redirect='login')
async def admin(request):
    ...


async def login(request):
    if request.method == "POST":
        # Now that the user is authenticated,
        # we can send them to their original request destination
        if request.user.is_authenticated:
            next_url = request.query_params.get("next")
            if next_url:
                return RedirectResponse(next_url)
            return RedirectResponse("/")

For class-based endpoints, you should wrap decorators around the methods of the class.

from starlette.authentication import requires
from starlette.endpoints import HTTPEndpoint


class Dashboard(HTTPEndpoint):
    @requires("authenticated")
    async def get(self, request):
        ...

Custom authentication error response

You can customize the error response sent when the authentication backend raises AuthenticationError:

from starlette.applications import Starlette
from starlette.middleware import Middleware
from starlette.middleware.authentication import AuthenticationMiddleware
from starlette.requests import Request
from starlette.responses import JSONResponse


def on_auth_error(request: Request, exc: Exception):
    return JSONResponse({"error": str(exc)}, status_code=401)

app = Starlette(
    middleware=[
        Middleware(AuthenticationMiddleware, backend=BasicAuthBackend(), on_error=on_auth_error),
    ],
)