Skip to content

Litestar Integration

keyshield provides first-class support for Litestar via keyshield.litestar_api. The integration exposes two helpers:

Helper Purpose
create_api_keys_router Full CRUD management router (same contract as the FastAPI counterpart)
create_api_key_guard Litestar guard that verifies Authorization: Bearer <key> on any route

Installation

uv add keyshield[litestar]

Quick start

from litestar import Litestar
from keyshield.litestar_api import create_api_keys_router, create_api_key_guard
from keyshield.services.base import ApiKeyService
from keyshield.repositories.in_memory import InMemoryApiKeyRepository
from keyshield.hasher.argon2 import Argon2ApiKeyHasher

# Shared service provider (called once per request by Litestar DI)
async def provide_svc() -> ApiKeyService:
    return ApiKeyService(
        repo=InMemoryApiKeyRepository(),
        hasher=Argon2ApiKeyHasher(pepper="your-secret-pepper"),
    )

# Management router (CRUD on /api-keys/*)
mgmt_router = create_api_keys_router(provide_svc=provide_svc)

app = Litestar(route_handlers=[mgmt_router])

Protecting routes with a guard

from litestar import Litestar, get
from litestar.connection import Request
from keyshield.litestar_api import create_api_key_guard

guard = create_api_key_guard(provide_svc=provide_svc)

# Apply to a single route
@get("/protected", guards=[guard])
async def protected_route(request: Request) -> dict:
    key = request.state.api_key   # verified ApiKey entity
    return {"key_id": key.key_id}

# Apply globally to the whole app
app = Litestar(route_handlers=[protected_route], guards=[guard])

Scope-restricted guard

admin_guard = create_api_key_guard(
    provide_svc=provide_svc,
    required_scopes=["admin"],
)

@get("/admin", guards=[admin_guard])
async def admin_route() -> dict:
    return {"admin": True}

Management endpoints

The router mounted by create_api_keys_router exposes the same REST API as the FastAPI integration:

Method Path Description
POST /api-keys/ Create a new key (returns plaintext once)
GET /api-keys/ List keys (paginated)
GET /api-keys/{id} Get a key by ID
PATCH /api-keys/{id} Partially update a key
DELETE /api-keys/{id} Delete a key
POST /api-keys/{id}/activate Activate a key
POST /api-keys/{id}/deactivate Deactivate a key
POST /api-keys/search Search with filters (paginated)
POST /api-keys/count Count keys matching a filter
POST /api-keys/verify Verify a key

SQLAlchemy repository

Use SqlAlchemyApiKeyRepository exactly as with FastAPI — Litestar's async lifecycle handles session management transparently:

from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine, async_sessionmaker
from keyshield.repositories.sql import SqlAlchemyApiKeyRepository, Base

engine = create_async_engine("postgresql+asyncpg://user:pass@localhost/db")
SessionLocal = async_sessionmaker(engine, class_=AsyncSession, expire_on_commit=False)

async def provide_svc() -> ApiKeyService:
    async with SessionLocal() as session:
        async with session.begin():
            repo = SqlAlchemyApiKeyRepository(session)
            return ApiKeyService(repo=repo, hasher=Argon2ApiKeyHasher(pepper="pepper"))

Caching

The CachedApiKeyService from keyshield.services.cached works identically in Litestar:

from keyshield.services.cached import CachedApiKeyService

async def provide_svc() -> CachedApiKeyService:
    return CachedApiKeyService(
        repo=InMemoryApiKeyRepository(),
        hasher=Argon2ApiKeyHasher(pepper="pepper"),
        cache_ttl=300,
    )

Dev mode (.env keys)

import os
os.environ["API_KEY_DEV"] = "ak_v1-mydevkeyid-mysecret64chars"

async def provide_svc() -> ApiKeyService:
    svc = ApiKeyService(repo=InMemoryApiKeyRepository(), hasher=Argon2ApiKeyHasher())
    await svc.load_dotenv(envvar_prefix="API_KEY_")
    return svc