Skip to content

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.

export LIGHTAPI_JWT_SECRET="your-secret-key-at-least-32-bytes-long"

Token format:

{
  "sub": "user-id",
  "is_admin": false,
  "exp": 1234567890
}

After successful authentication, the decoded payload is stored in request.state.user.

Behaviour:

  • OPTIONS requests are always allowed (CORS preflight compatibility).
  • Returns 401 Unauthorized if 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

from lightapi import 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 if backend=JWTAuthentication is set. This lets you make individual endpoints or individual verbs publicly readable while keeping writes protected.

class ArticleEndpoint(RestEndpoint):
    title: str
    class Meta:
        authentication = Authentication(
            backend=JWTAuthentication,
            permission={"GET": AllowAny, "POST": IsAuthenticated},
        )
# GET /articles — no token needed
# POST /articles — valid JWT required

IsAuthenticated

from lightapi import IsAuthenticated

Allows access only if the authentication backend returns True (valid credentials present).

IsAdminUser

from lightapi import 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)