KemalIdentity::Kemal
The Kemal adapter.
This is the only layer that knows HTTP::Server::Context exists. Everything under
src/kemal_identity/{core,accounts,sessions,passwords} is framework-agnostic, which is what
makes spec/unit runnable without a server and leaves the door open to an Amber or Lucky
adapter without touching the core (docs/01-architecture.md).
Wiring
require "kemal"
require "kemal_identity/kemal"
KemalIdentity.configure(
accounts: MyAccountRepository.new,
sessions: KemalIdentity::Postgres::SessionRepository.new(db),
)
use KemalIdentity::Kemal::ErrorHandler.new # outermost: catches what guards raise
use KemalIdentity::Kemal::AuthenticationHandler.new # populates env.auth; never rejects
use KemalIdentity::Kemal::CSRFHandler.new # after authentication: the token binds to the session
use KemalIdentity::Kemal::PathGuard.new(prefix: "/admin")
Order matters and is not obvious, so it is spelled out in the README. Two rules: nothing
authentication-related is registered at position 0 (that position takes over Kemal's
temporary-file cleanup), and ErrorHandler goes outside anything that raises.
Class methods
Checks that the handler chain is in an order that works, at boot rather than at the first request.
Order here is a security property and a silent one: every wrong arrangement compiles, most of them start, and the symptom arrives later as a 500 where a 401 belonged, a CSRF token that anchors on nothing, or a guard reading a principal that has not been resolved yet.
use KemalIdentity::Kemal::ErrorHandler.new(login_path: "/login")
use KemalIdentity::Kemal::AuthenticationHandler.new
use KemalIdentity::Kemal::CSRFHandler.new
KemalIdentity::Kemal.validate_middleware_order!
Kemal.run
Raises ConfigurationError naming every problem it found, not just the first — a chain with
two things wrong should take one round trip to fix, which is what Django's system checks do
with admin.E408 and its siblings. ASP.NET Core goes further and ships a compile-time
analyzer (ASP0001) for the same class of mistake; Crystal gives no equivalent hook, so this
runs at startup and an application opts in by calling it. That call is the escape hatch:
there is no list of silenced checks, because an application that disagrees simply does not
ask.
What it does not check
The relative order of CSRFHandler and PathGuard, which the documented chain separates
with the application's own middleware. Both run after authentication, both refuse, and
neither depends on the other — so an application that puts its guard earlier has made a
choice rather than a mistake, and this is not the place to argue with it.
Nor anything about handlers this shard did not write. It reads the chain to find its own.