KemalIdentity::Kemal::PathGuard
Inherits Kemal::Handler < Kemal::HandlerInterface < HTTP::Handler < Reference < Object
Requires authentication for a whole path subtree.
It matches the path itself, for every method
Deliberately not only ["/admin/*"]. On Kemal 1.10.0 – 1.12.0 that rule defaults to GET
and does not match HEAD, so middleware scoped that way never ran on a HEAD request
while the GET handler executed and returned the headers it set. 1.13.0 fixes it; the
floor does not have the fix, and matching on the path alone is immune either way.
The same property makes new methods safe by default: HTTP QUERY arrived in Kemal 1.13.0 and this guard covered it without a change, because there was never a list of methods to forget to update.
use KemalIdentity::Kemal::PathGuard.new(prefix: "/admin")
use KemalIdentity::Kemal::PathGuard.new(prefix: "/account", within: 5.minutes)
Reject before touching params
The guard answers from the request path alone and never reads env.params. On Kemal
1.10.0 – 1.12.0, anything that parsed a multipart body and then rejected the request
leaked the temporary files it spooled, permanently — so an unauthenticated client could
fill the disk one rejected upload at a time (docs/04-kemal-integration.md).
Constructors
within turns this into a step-up guard: the subtree then needs authentication that is
also recent, and a session restored from a remember-me cookie will never satisfy it.
credentials says which kinds of credential the subtree accepts, for the monolith that
serves pages and an API from one process:
use KemalIdentity::Kemal::PathGuard.new(
prefix: "/api", credentials: [KemalIdentity::CredentialKind::ApiToken, KemalIdentity::CredentialKind::Jwt]
)
A credential of another kind is refused with ForbiddenError — a 403, not a 401: the
caller is authenticated, they are using the wrong door, and telling them to log in again
would be a loop. The refusal is deliberately after require!, so an anonymous request
is still a 401 and nobody learns which credential classes a subtree takes without first
holding one.
CredentialKind::Custom covers every credential an application's own
RequestAuthenticator establishes, so a deployment with two custom families cannot tell
them apart here — CredentialRef#name is what distinguishes those, and a subtree that
needs to select on it writes its own handler (blueprints/0021).
Instance methods
Whether this guard covers path.
The subtree, and only the subtree. /admin guards /admin, /admin/, and
/admin/users — and pointedly not /administrators, which a naive
starts_with? would hand to an attacker as an unguarded path that looks guarded.