Skip to content

Authentication

Marko’s authentication system uses a guard-based architecture. Guards handle the “how” of authentication (sessions, tokens, API keys), while user providers handle the “where” (database, LDAP, external API).

Terminal window
composer require marko/authentication

Configure guards in config/auth.php:

config/auth.php
<?php
declare(strict_types=1);
return [
'defaults' => [
'guard' => 'web',
],
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'database',
],
'api' => [
'driver' => 'token',
'provider' => 'database',
],
],
'providers' => [
'database' => [
'driver' => 'database',
'table' => 'users',
],
],
];

Inject AuthManager to check authentication state:

app/dashboard/Controller/DashboardController.php
<?php
declare(strict_types=1);
namespace App\Dashboard\Controller;
use Marko\Authentication\AuthManager;
use Marko\Routing\Attributes\Get;
use Marko\Routing\Attributes\Middleware;
use Marko\Authentication\Middleware\AuthMiddleware;
use Marko\Http\ResponseInterface;
use Marko\Http\JsonResponse;
class DashboardController
{
public function __construct(
private readonly AuthManager $authManager,
) {}
#[Get('/dashboard')]
#[Middleware(AuthMiddleware::class)]
public function index(): ResponseInterface
{
$user = $this->authManager->user();
return new JsonResponse(data: [
'message' => "Welcome, {$user->name}",
]);
}
}

For traditional web applications with cookie-based sessions:

// Login
$this->auth->guard('web')->attempt([
'email' => $email,
'password' => $password,
]);
// Check if authenticated
$this->auth->guard('web')->check(); // bool
// Get the authenticated user
$this->auth->guard('web')->user(); // AuthenticatableInterface|null
// Logout
$this->auth->guard('web')->logout();

For APIs using bearer tokens:

// Authenticate from the request's Authorization header
$this->auth->guard('api')->user();

Marko provides two authentication middleware classes:

use Marko\Authentication\Middleware\AuthMiddleware; // Must be logged in
use Marko\Authentication\Middleware\GuestMiddleware; // Must NOT be logged in
#[Get('/dashboard')]
#[Middleware(AuthMiddleware::class)]
public function dashboard(): ResponseInterface { /* ... */ }
#[Get('/login')]
#[Middleware(GuestMiddleware::class)]
public function loginForm(): ResponseInterface { /* ... */ }

Create your own authentication strategy by implementing GuardInterface:

app/myapp/Auth/ApiKeyGuard.php
<?php
declare(strict_types=1);
namespace App\MyApp\Auth;
use Marko\Authentication\Contracts\GuardInterface;
use Marko\Authentication\AuthenticatableInterface;
class ApiKeyGuard implements GuardInterface
{
public function check(): bool
{
return $this->user() !== null;
}
public function guest(): bool
{
return !$this->check();
}
public function user(): ?AuthenticatableInterface
{
// Look up user by API key from request header
}
public function id(): int|string|null
{
return $this->user()?->getAuthIdentifier();
}
public function attempt(array $credentials): bool
{
// Validate API key credentials
}
public function login(AuthenticatableInterface $user): void
{
// Store the authenticated user
}
public function loginById(int|string $id): ?AuthenticatableInterface
{
// Find and log in a user by ID
}
public function logout(): void
{
// Invalidate the API key
}
public function getName(): string
{
return 'api-key';
}
}

Register it via Preference in your module.php:

module.php
use Marko\Authentication\Contracts\GuardInterface;
use App\MyApp\Auth\ApiKeyGuard;
return [
'bindings' => [
GuardInterface::class => ApiKeyGuard::class,
],
];

The authentication system dispatches events you can observe:

EventWhen
LoginEventSuccessful login
LogoutEventUser logs out
FailedLoginEventLogin attempt fails
PasswordResetEventPassword is reset
module.php
use Marko\Authentication\Event\FailedLoginEvent;
use App\Security\Observer\LockoutAfterFailures;
return [
'observers' => [
FailedLoginEvent::class => [
LockoutAfterFailures::class,
],
],
];