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),
],
)