cakephp/authentication
cakephp/authentication is a flexible authentication and authorization plugin for CakePHP 4 and 5. It offers pluggable authenticators (session, form, token, JWT, OAuth), middleware integration, identity management and password hashing, making it easy to secure your app.
Install with Composer and decide up front which world you're integrating into, because this is a CakePHP-first package with a usable PSR-7 core:
composer require cakephp/authentication
CakePHP path (the intended one). First stop is the docs' "Quick Start" and AuthenticationService configuration:
bin/cake plugin load AuthenticationAuthenticationMiddleware to src/Application.php::middleware() — order matters, it must run after SessionMiddleware and before your app/routing middleware.Application::getAuthenticationService(ServerRequestInterface $request) and define two things separately:
Form, Session, Jwt, HttpBasic, Token)Password, Orm, JwtSubject, Token)Authentication.Authentication component in AppController (or per-controller), then read $this->request->getAttribute('identity').Your first use case should be the boring one: a users table with email/password, a FormAuthenticator + PasswordIdentifier + OrmIdentifier, a SessionAuthenticator for subsequent requests, and a login action that calls $service->getResult()->isValid().
PSR-7 path. If you're a Laravel developer eyeing this package, be honest about the fit. Laravel's own Auth guards, Sanctum, and Passport already cover the framework's request lifecycle, and cakephp/authentication does not plug into the Auth facade, Eloquent, or Laravel's middleware pipeline. The realistic ways to use it from Laravel are:
$service->authenticate($request) returns a result you inspect: isValid(), getStatus(), getErrors(), getData().PasswordIdentifier, DefaultPasswordHasher) for a bespoke auth flow, ignoring the CakePHP integration entirely.If you're bridging into Laravel, expect to write a PSR-7 request factory and a small middleware adapter yourself. Don't budget for it being a drop-in.
Authenticator/Identifier split is the mental model. An authenticator produces a "success/failure" and may persist identity; an identifier turns credentials into a user record. You compose them independently, so the same OrmIdentifier can back both a FormAuthenticator and a JwtSubjectIdentifier. This is the pattern that makes the package flexible, and it's the one to internalize first.
Stateful web (session) flow. Configure FormAuthenticator first with SessionAuthenticator second. On POST /login, FormAuthenticator validates and persists to session; on later requests, SessionAuthenticator reads the session and re-hydrates. Keep the session authenticator's identifier SessionIdentifier pointed at the session key you persist.
Stateless API flow. Use only JwtAuthenticator/TokenAuthenticator/HttpBasicAuthenticator — never mix in SessionAuthenticator on API routes, or you'll silently get cookie-based auth on endpoints you intended to be token-only. Configure JwtAuthenticator with firebase/php-jwt and JwtSubjectIdentifier reading the sub claim.
Multiple credential shapes. To support login-by-email-or-username, extend PasswordIdentifier or use OrmIdentifier with a custom finder and a fields map. For multi-tenant, use a custom identifier that scopes by tenant header before querying.
Custom extension points. For anything nonstandard (API keys, magic links, SSO), implement AuthenticatorInterface and/or IdentifierInterface. Register via $service->loadAuthenticator('MyAuth') or in the authenticators config array. This is cleaner than fighting FormAuthenticator into shapes it doesn't want.
Login/logout actions. Use $service->getResult() rather than inspecting $request->getAttribute('identity') immediately after authenticate — the result carries failure status and errors. Logout is $service->clearIdentity($request, $response) wrapped in whatever response handling your stack needs.
Events for observability. Authentication.afterIdentify and Authentication.failed are your hooks for audit logging, rate-limiting, and rehashing passwords on successful login (check needsRehash() via your password hasher). Wire them once in getAuthenticationService rather than per-controller.
Authorization is a separate package. Don't try to make this package do roles/permissions — that's cakephp/authorization (or your own policy layer). Keeping the two apart is the intended architecture.
Laravel integration tip. If you must reuse pieces: instantiate AuthenticationService behind a small PSR-7 adapter and expose the resulting identity into Laravel via a custom guard or request attribute. Alternatively, run it in parallel with native guards on a distinct route group (e.g., a legacy SSO endpoint) rather than replacing the default web/api guards.
Identifiers are shared, not per-authenticator. A common mistake is assuming each authenticator gets its own identifier config; they draw from the single identifiers array. If one flow needs password auth and another needs token auth, both identifiers coexist and each authenticator picks what it needs — but misconfigured shared identifiers cause surprising cross-talk.
Sessions must be started before the middleware. If AuthenticationMiddleware runs before SessionMiddleware, the session authenticator silently fails. Order: Session → Authentication → routing/app.
Unvalidated requests do not throw. The middleware sets identity only on success. If your controller assumes an identity and the result was a failure, you'll get a null-attribute fatal. Decide early whether you're using the auth component's automatic redirect (unauthenticatedRedirect + queryParam) or explicit checks — don't mix the two.
Password rehashing is on you. PasswordIdentifier verifies, but doesn't upgrade legacy hashes. Hook Authentication.afterIdentify and call needsRehash()/rehash there, or you'll keep old hashes forever.
JWT clock skew and algorithm pinning. JwtAuthenticator needs the algorithm explicitly set and will throw on mismatch. Also check the exp/nbf handling against your issuer — clock skew on distributed systems bites here first.
Stateless means stateless. Don't use SessionAuthenticator on API routes "just in case"; it enables the persistence path on requests that should never write a session. Better: separate AuthenticationService configs per route group, or gate via middleware.
Form login and CSRF. If you're using CakePHP's FormProtection, the login POST needs a token; otherwise you'll chase a 403 that has nothing to do with credentials. Alternatively exempt the login route deliberately and document why.
Debugging failures. $result->getStatus() plus $result->getErrors() tells you whether the failure was credentials, missing fields, or a misconfigured identifier. Authentication.failed event listeners are the fastest path to a log line without touching controllers.
Config location drift. getAuthenticationService moved signature across CakePHP 4/5; copy-pasting snippets across versions is the most common source of "authenticator not found" errors. Verify against the version in your composer.lock, not the latest docs.
PSR-7 caveat. Outside CakePHP you get the service but not the request-attribute helpers, component, or automatic redirect — you must extract identity from the result and attach it to your request yourself. That's a small amount of glue, but it's real work; don't assume CakePHP examples translate one-to-one.
How can I help you explore Laravel packages today?