KemalIdentity::Passwords::Authenticator
Verifies a login and a password, and answers who — if anyone — that proves.
A CredentialAuthenticator, not a RequestAuthenticator: it runs at login only, takes an
identifier and a secret, and establishes an identity rather than reading one that was
already established. Passport.js is the cautionary example of forcing both into one
abstraction (docs/01-architecture.md).
It does not create a session. It returns a Principal with no session_id, and the
caller passes the account to Sessions::Service#start, which is what makes rotation on
login — the session fixation defence — the session layer's decision rather than this
one's.
Two ways this class avoids being an account oracle
Every failure is one failure. reason distinguishes them for the audit log; the
response must not. DisabledAccount and InvalidCredential rendering differently tells
an attacker which logins exist (docs/04-kemal-integration.md).
Every path costs the same. The naive implementation returns early when no account
matches, having done no hashing, and the response arrives a hundred-odd milliseconds
sooner — a reliable oracle no matter how identical the body is. So an unknown login is
verified against Hasher#dummy_digest, and the disabled check happens after the
verification rather than in front of it, so that disabled accounts are not distinguishable
by timing either.
Constructors
Instance methods
Verifies password against the account identified by login.
ip keys the source-address half of the rate limit, and is recorded in the audit event.
It must be the client's address. Behind a reverse proxy, remote_address is the
proxy, so every request in the deployment shares one address key and the first attacker
to reach the limit denies logins to everybody — the limiter becomes an availability
problem rather than a defence. An application terminating behind one derives this from
its own trusted-proxy configuration, counting from the right of X-Forwarded-For by the
number of proxies it actually operates. Never the leftmost value and never the whole
header: a client picks those, so a spoofed one mints a fresh allowance per request.
nil when there is genuinely no address to attribute — a request off a unix socket, an
internal caller. That consumes the login key alone, which is the honest reading rather
than a placeholder every caller would share.