Skip to content

marko/authentication

Session and token-based authentication — guards protect routes, events track activity, middleware controls access. The auth package provides flexible authentication with two built-in guards: SessionGuard for web applications and TokenGuard for APIs. Configure multiple guards, implement custom user providers, and react to authentication events via observers.

Terminal window
composer require marko/authentication

The package reads configuration from config/authentication.php:

config/authentication.php
return [
'default' => [
'guard' => 'session',
'provider' => 'users',
],
'guards' => [
'session' => [
'driver' => 'session',
'provider' => 'users',
],
'token' => [
'driver' => 'token',
'provider' => 'users',
],
],
'providers' => [
'users' => [
'driver' => 'database',
'table' => 'users',
],
],
'password' => [
'driver' => 'bcrypt',
'bcrypt' => [
'cost' => 12,
],
],
'remember' => [
'expiration' => 43200, // 30 days in minutes
'cookie' => 'remember_token',
],
];

Inject AuthManager to check if a user is authenticated:

DashboardController.php
use Marko\Authentication\AuthManager;
class DashboardController
{
public function __construct(
private AuthManager $authManager,
) {}
public function index(): Response
{
if ($this->authManager->check()) {
$user = $this->authManager->user();
return new Response("Welcome, user {$this->authManager->id()}");
}
return Response::redirect('/login');
}
}

Use attempt() to authenticate with credentials:

public function login(
Request $request,
): Response {
$credentials = [
'email' => $request->get('email'),
'password' => $request->get('password'),
];
if ($this->authManager->attempt($credentials)) {
return Response::redirect('/dashboard');
}
return new Response('Invalid credentials', 401);
}
public function logout(): Response
{
$this->authManager->logout();
return Response::redirect('/');
}

Your user model must implement AuthenticatableInterface:

User.php
use Marko\Authentication\AuthenticatableInterface;
class User implements AuthenticatableInterface
{
public function __construct(
private int $id,
private string $email,
private string $password,
private ?string $rememberToken = null,
) {}
public function getAuthIdentifier(): int|string
{
return $this->id;
}
public function getAuthIdentifierName(): string
{
return 'id';
}
public function getAuthPassword(): string
{
return $this->password;
}
public function getRememberToken(): ?string
{
return $this->rememberToken;
}
public function setRememberToken(
?string $token,
): void {
$this->rememberToken = $token;
}
public function getRememberTokenName(): string
{
return 'remember_token';
}
}

Guards define how users are authenticated. The package includes two built-in guards.

The default guard for web applications. Stores user ID in the session and supports “remember me” functionality:

// Use specific guard
$guard = $this->authManager->guard('session');
// Login with remember me
$guard->login($user, remember: true);
// Check via session
if ($guard->check()) {
$user = $guard->user();
}

For API authentication via Bearer tokens in the Authorization header:

$guard = $this->authManager->guard('token');
// TokenGuard extracts token from Authorization header
// Authorization: Bearer your-api-token
if ($guard->check()) {
$user = $guard->user();
}

Implement GuardInterface to create custom guards:

JwtGuard.php
use Marko\Authentication\AuthenticatableInterface;
use Marko\Authentication\Contracts\GuardInterface;
use Marko\Authentication\Contracts\UserProviderInterface;
class JwtGuard implements GuardInterface
{
private ?UserProviderInterface $provider = null;
public function check(): bool
{
return $this->user() !== null;
}
public function guest(): bool
{
return !$this->check();
}
public function user(): ?AuthenticatableInterface
{
// Decode JWT and retrieve user
}
public function id(): int|string|null
{
return $this->user()?->getAuthIdentifier();
}
public function attempt(
array $credentials,
): bool {
// JWT guards typically don't use attempt()
return false;
}
public function login(
AuthenticatableInterface $user,
): void {
// Generate and return JWT
}
public function loginById(
int|string $id,
): ?AuthenticatableInterface {
return null;
}
public function logout(): void {
// Invalidate token
}
public function setProvider(
UserProviderInterface $provider,
): void {
$this->provider = $provider;
}
public function getName(): string
{
return 'jwt';
}
}

Protects routes by requiring authentication:

DashboardController.php
use Marko\Authentication\Middleware\AuthMiddleware;
use Marko\Routing\Attributes\Get;
use Marko\Routing\Attributes\Middleware;
class DashboardController
{
#[Get('/dashboard')]
#[Middleware(AuthMiddleware::class)]
public function index(): Response
{
// Only authenticated users reach here
}
}

For web routes, configure a redirect:

// In module.php bindings
new AuthMiddleware(
authManager: $authManager,
redirectTo: '/login',
);

For API routes using TokenGuard, unauthenticated requests receive a 401 JSON response:

{"error": "Unauthorized"}

Restricts routes to unauthenticated users (e.g., login pages):

LoginController.php
use Marko\Authentication\Middleware\GuestMiddleware;
class LoginController
{
#[Get('/login')]
#[Middleware(GuestMiddleware::class)]
public function show(): Response
{
// Only guests can view the login page
}
}

Authenticated users are redirected to a configured path (default: /).

The auth package dispatches events during the authentication lifecycle. Create observers to react to these events.

Dispatched when a user successfully logs in:

LogLoginObserver.php
use Marko\Authentication\Event\LoginEvent;
use Marko\Core\Attributes\Observer;
#[Observer(LoginEvent::class)]
class LogLoginObserver
{
public function handle(
LoginEvent $event,
): void {
$user = $event->getUser();
$guard = $event->getGuard();
$remember = $event->getRemember();
// Log the login, update last_login timestamp, etc.
}
}

Dispatched when a user logs out:

LogLogoutObserver.php
use Marko\Authentication\Event\LogoutEvent;
use Marko\Core\Attributes\Observer;
#[Observer(LogoutEvent::class)]
class LogLogoutObserver
{
public function handle(
LogoutEvent $event,
): void {
$user = $event->getUser();
$guard = $event->getGuard();
// Clean up user session data, log activity, etc.
}
}

Dispatched when authentication fails (credentials not found or invalid):

LogFailedLoginObserver.php
use Marko\Authentication\Event\FailedLoginEvent;
use Marko\Core\Attributes\Observer;
#[Observer(FailedLoginEvent::class)]
class LogFailedLoginObserver
{
public function handle(
FailedLoginEvent $event,
): void {
$credentials = $event->getCredentials(); // Password removed for security
$guard = $event->getGuard();
// Log failed attempt, implement rate limiting, alert on suspicious activity
}
}

Dispatched when a user’s password is reset:

NotifyPasswordResetObserver.php
use Marko\Authentication\Event\PasswordResetEvent;
use Marko\Core\Attributes\Observer;
#[Observer(PasswordResetEvent::class)]
class NotifyPasswordResetObserver
{
public function handle(
PasswordResetEvent $event,
): void {
$user = $event->getUser();
// Send confirmation email, invalidate other sessions, etc.
}
}
public function guard(?string $name = null): GuardInterface;
public function check(): bool;
public function user(): ?AuthenticatableInterface;
public function id(): int|string|null;
public function attempt(array $credentials): bool;
public function logout(): void;
public function check(): bool;
public function guest(): bool;
public function user(): ?AuthenticatableInterface;
public function id(): int|string|null;
public function attempt(array $credentials): bool;
public function login(AuthenticatableInterface $user): void;
public function loginById(int|string $id): ?AuthenticatableInterface;
public function logout(): void;
public function setProvider(UserProviderInterface $provider): void;
public function getName(): string;
public function getAuthIdentifier(): int|string;
public function getAuthIdentifierName(): string;
public function getAuthPassword(): string;
public function getRememberToken(): ?string;
public function setRememberToken(?string $token): void;
public function getRememberTokenName(): string;
public function retrieveById(int|string $identifier): ?AuthenticatableInterface;
public function retrieveByCredentials(array $credentials): ?AuthenticatableInterface;
public function validateCredentials(AuthenticatableInterface $user, array $credentials): bool;
public function retrieveByRememberToken(int|string $identifier, string $token): ?AuthenticatableInterface;
public function updateRememberToken(AuthenticatableInterface $user, ?string $token): void;