Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laminas Authentication Laravel Package

laminas/laminas-authentication

Laminas Authentication provides a flexible API for authenticating users in PHP applications. It ships with adapters for HTTP Basic and Digest, LDAP, and relational databases, plus persistent identity storage and result objects for building custom login flows.

Deep Wiki
Context7

Getting Started

Install the package and a session backend if you want persistent login state:

composer require laminas/laminas-authentication laminas/laminas-session

Start by reading four things:

  • Laminas\Authentication\AuthenticationService — the orchestrator.
  • Laminas\Authentication\Adapter\AdapterInterface — verifies credentials.
  • Laminas\Authentication\Storage\StorageInterface — persists identity.
  • Laminas\Authentication\Result — success/failure result object.

The smallest useful flow is a login form. Build an adapter with the submitted identity and credential, pass it to AuthenticationService::authenticate(), then check the Result:

use Laminas\Authentication\AuthenticationService;
use Laminas\Authentication\Storage\Session;

$auth = new AuthenticationService(new Session('myapp_auth'));

$adapter = new App\Auth\UserRepositoryAdapter($users);
$adapter->setIdentity($email)->setCredential($password);

$result = $auth->authenticate($adapter);

if ($result->isValid()) {
    // Identity is now written to session storage by AuthenticationService.
    // $auth->hasIdentity() === true
    // $auth->getIdentity() === whatever the adapter returned as identity (e.g. user ID)
    header('Location: /dashboard');
    exit;
}

foreach ($result->getMessages() as $message) {
    // show login error
}

On later requests, the same AuthenticationService instance—wired with the same session storage—can answer:

if (!$auth->hasIdentity()) {
    // redirect to /login
}

$userId = $auth->getIdentity();

Logout is just clearing storage:

$auth->clearIdentity();

That login → persist identity → check identity → logout loop is the core day-to-day use case.

Implementation Patterns

Register one shared AuthenticationService per request. In Laminas MVC use service_manager.factories; in Mezzio use dependencies.factories. Give it session storage, and pass the adapter at login time:

use Laminas\Authentication\AuthenticationService;
use Laminas\Authentication\Storage\Session;
use Laminas\Session\SessionManager;

return [
    'dependencies' => [
        'factories' => [
            AuthenticationService::class => static function ($container) {
                $sessionManager = $container->get(SessionManager::class);
                $storage = new Session('myapp_auth', 'storage', $sessionManager);
                return new AuthenticationService($storage);
            },
        ],
    ],
];

In Laminas MVC, the equivalent key is service_manager instead of dependencies.

Use a custom adapter for real applications. The built-in DbTable adapters are convenient, but most modern apps use password_hash()/password_verify(), which cannot be expressed well as a SQL credential treatment. A repository-backed adapter is usually cleaner:

namespace App\Auth;

use Laminas\Authentication\Adapter\AdapterInterface;
use Laminas\Authentication\Result;

final class UserRepositoryAdapter implements AdapterInterface
{
    private string $identity = '';
    private string $credential = '';

    public function __construct(private UserRepository $users) {}

    public function setIdentity(string $identity): self
    {
        $this->identity = $identity;
        return $this;
    }

    public function setCredential(string $credential): self
    {
        $this->credential = $credential;
        return $this;
    }

    public function authenticate(): Result
    {
        $user = $this->users->findByEmail($this->identity);

        if ($user === null) {
            return new Result(
                Result::FAILURE_IDENTITY_NOT_FOUND,
                null,
                ['Invalid credentials']
            );
        }

        if (!password_verify($this->credential, $user->passwordHash)) {
            return new Result(
                Result::FAILURE_CREDENTIAL_INVALID,
                null,
                ['Invalid credentials']
            );
        }

        if (password_needs_rehash($user->passwordHash, PASSWORD_DEFAULT)) {
            $this->users->updatePasswordHash(
                $user->id,
                password_hash($this->credential, PASSWORD_DEFAULT)
            );
        }

        return new Result(Result::SUCCESS, $user->id, ['Authenticated']);
    }
}

Then at login:

$adapter = new UserRepositoryAdapter($users);
$adapter->setIdentity($email)->setCredential($password);

$result = $auth->authenticate($adapter);

Store a stable identity, not a full mutable user object. The session storage serializes whatever the adapter returns. A user ID or UUID is safest. On each request, use getIdentity() to load the current user from your repository. This avoids stale session data and serialization problems when user classes change.

Use middleware/controller guards. In Mezzio, inject AuthenticationService into middleware and reject unauthenticated requests:

public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
    if (!$this->auth->hasIdentity()) {
        return new RedirectResponse('/login');
    }

    return $handler->handle(
        $request->withAttribute('user_id', $this->auth->getIdentity())
    );
}

In Laminas MVC, inject the same service into controllers or wrap it in your own controller plugin.

Use NonPersistent storage for tests and stateless paths. It keeps identity only for the current object lifetime:

use Laminas\Authentication\Storage\NonPersistent;

$auth = new AuthenticationService(new NonPersistent(), $adapter);
$result = $auth->authenticate();

This is useful for API-style authentication where every request re-validates a token, or for unit tests where you do not want session side effects.

Use Chain storage when you need multiple persistence layers. For example, you can try a session storage first and fall back to another storage implementation. Chain reads the first non-empty storage and writes/clears across the configured chain—useful for custom “remember me” or migration scenarios.

For LDAP/HTTP/Digest, treat the built-in adapters as integration points. The LDAP adapter works with laminas/laminas-ldap; the HTTP adapter handles HTTP basic/digest auth; the Digest adapter is for legacy digest auth. In modern APIs, a custom bearer-token adapter plus NonPersistent storage is usually a better fit than the HTTP adapter.

Gotchas and Tips

  • Always check $result->isValid(). authenticate() does not throw on bad credentials; it returns a Result. A failed result also does not write anything to storage.
  • Constructor order is storage first, adapter second. new AuthenticationService($storage, $adapter) — reversing them causes type errors or confusing behavior.
  • Set an identity on success. Result::SUCCESS with a null identity can make hasIdentity() behave unexpectedly because many storage implementations treat null as empty.
  • Use hasIdentity() instead of truthiness checks. getIdentity() can return 0, '', or another falsy-but-valid value. hasIdentity() checks the storage directly.
  • Start the session before authenticating. Session storage writes during authenticate(). If the session has not started, or headers have already been sent, login can fail or silently lose the identity. In Mezzio, start the session in middleware; in Laminas MVC, configure the session manager/bootstrap.
  • Set an application-specific session namespace. The default namespace can collide with other apps or packages. Use something like new Session('myapp_auth').
  • Regenerate the session ID after successful login. laminas-authentication does not do this for you. Call session_regenerate_id(true) or the equivalent on your session manager to reduce session fixation risk.
  • Do not leak account existence. Return the same “Invalid credentials” message for both missing identity and bad password, even if you log the more specific Result code server-side.
  • Beware the legacy DbTable adapters. They are convenient for simple SQL-backed credentials, but credential treatments are SQL fragments and do not work with password_hash(). For new code, prefer a custom adapter using password_verify().
  • Use generic result messages and map codes yourself. Result::FAILURE_IDENTITY_NOT_FOUND, FAILURE_CREDENTIAL_INVALID, FAILURE_IDENTITY_AMBIGUOUS, and FAILURE_UNCATEGORIZED are useful for logging; user-facing messages should usually be vague.
  • Do not share AuthenticationService across long-running requests. In RoadRunner, Swoole, or other persistent workers, create the service and session storage per request. Sharing them can leak one user’s identity into another request.
  • Clear identity on logout, but do not expect the adapter to reset. clearIdentity() clears storage only. If you reuse the service, set or replace the adapter for the next authentication attempt.
  • Calling authenticate() with no adapter throws. Either pass an adapter to authenticate($adapter) or call setAdapter() first.
  • Session serialization matters. If you store an object as identity, ensure it is serializable and autoloadable on every request. A scalar user ID is almost always safer.
  • Add rate limiting and CSRF protection outside the package. laminas-authentication validates credentials; it does not protect login forms from brute force or CSRF. Put those controls in your login controller/middleware.
  • Use NonPersistent storage in tests. It keeps tests fast and avoids session state bleeding between test cases.
  • Debug failures with the result object first. Log getCode() and getMessages() before inspecting adapters or storage. For session issues, inspect the configured namespace in $_SESSION; for adapter issues, reproduce the adapter call in isolation with a known-good and known-bad credential.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings
coleus/apps
seampact/laravel
emmyconceptng/laravel-ng-postcode
jjsquady/round-robin