Authentication API Reference
Overview
Authentication is per-endpoint and configured via Meta.authentication:
from lightapi import (
RestEndpoint,
Authentication, JWTAuthentication, IsAuthenticated,
)
class PostEndpoint(RestEndpoint):
title: str
class Meta:
authentication = Authentication(
backend=JWTAuthentication,
permission=IsAuthenticated,
)
Authentication
from lightapi import Authentication
# or: from lightapi.config import Authentication
Authentication(
backend: type | None = None,
permission: type | dict[str, type] | None = None,
)
| Parameter | Type | Description |
|---|---|---|
backend |
type \| None |
Authentication backend class. None = unauthenticated (allow all). |
permission |
type \| dict[str, type] \| None |
Permission class applied to all methods, or a {method: class} dict for per-method control. None = AllowAny. |
JWTAuthentication
JWT authentication using Authorization: Bearer <token> headers.
Required: LIGHTAPI_JWT_SECRET environment variable must be set.
Token format:
After successful authentication, the decoded payload is stored in request.state.user.
Behaviour:
- OPTIONS requests are always allowed (CORS preflight compatibility).
- Returns
401 Unauthorizedif the token is missing, malformed, or expired.
Auto-registered login endpoint
When at least one endpoint configures Authentication(backend=JWTAuthentication)
(or BasicAuthentication), LightAPI automatically registers
POST /auth/login and POST /auth/token. Pass a login_validator callable
to LightApi(...) to plug in your credential check:
def login_validator(username: str, password: str):
if username == "admin" and password == "secret":
return {"sub": "1", "username": "admin", "is_admin": True}
return None
app = LightApi(engine=engine, login_validator=login_validator)
Returns None to reject a login attempt.
Raising an exception from login_validator is treated identically to returning None — the client receives 401 Unauthorized. The exception is logged at WARNING level and is never surfaced in the response body.
The returned dict becomes the JWT payload. For JWT apps the response is
{"token": "<jwt>", "user": {...}}. The login route is rate-limited — see
Rate Limiting. Override the base path via
LightApi(auth_path="/api/auth").
Permission Classes
AllowAny
No authentication check. All requests are allowed. This is the default when permission=None.
Important: Setting
permission=AllowAny(either directly or via a per-method dict) completely bypasses the authentication backend — no token is required even ifbackend=JWTAuthenticationis set. This lets you make individual endpoints or individual verbs publicly readable while keeping writes protected.
IsAuthenticated
Allows access only if the authentication backend returns True (valid credentials present).
IsAdminUser
Allows access only if the JWT payload contains "is_admin": true.
Per-method permissions
Pass a dict[str, type] to apply different permission classes per HTTP method:
from lightapi import Authentication
from lightapi.authentication import JWTAuthentication, IsAuthenticated, IsAdminUser, AllowAny
class ArticleEndpoint(RestEndpoint):
title: str
class Meta:
authentication = Authentication(
backend=JWTAuthentication,
permission={
"GET": AllowAny,
"POST": IsAuthenticated,
"PUT": IsAuthenticated,
"PATCH": IsAuthenticated,
"DELETE": IsAdminUser,
},
)
BaseAuthentication
Implement to create a custom authentication backend:
from lightapi.authentication import BaseAuthentication
class ApiKeyAuthentication(BaseAuthentication):
def authenticate(self, request) -> bool:
key = request.headers.get("X-Api-Key")
return key == "expected-key"
BasePermission
Implement to create a custom permission class:
from lightapi.authentication import BasePermission
class IsOwner(BasePermission):
def has_permission(self, request) -> bool:
user = getattr(request.state, "user", None)
if not user:
return False
return str(user.get("sub")) == str(request.path_params.get("id"))
Response codes
| Situation | Status |
|---|---|
| Missing or invalid token | 401 Unauthorized |
| Valid token, insufficient permission | 403 Forbidden |
| OPTIONS request | Always 200 (CORS preflight) |