# Authentication Guide ## Installation ```bash composer require firebase/php-jwt ``` ## Core Value Objects ### Value Objects ```php // src/Core/Auth/ValueObjects/UserId.php final class UserId { public function __construct(public readonly int $value) {} public static function fromInt(int $id): self { return new self($id); } } // src/Core/Auth/ValueObjects/Email.php final class Email { public function __construct(public readonly string $value) {} public static function fromString(string $email): self { return new self($email); } } // src/Core/Auth/ValueObjects/HashedPassword.php final class HashedPassword { public function __construct(public readonly string $hash) {} public static function fromPlainText(string $password): self { return new self(password_hash($password, PASSWORD_ARGON2ID)); } public function verify(string $password): bool { return password_verify($password, $this->hash); } } // src/Core/Application/ValueObjects/AuthConfig.php final class AuthConfig { public function __construct( public readonly string $jwtSecret, public readonly int $jwtAccessTokenTtl, public readonly int $sessionLifetime, public readonly int $maxLoginAttempts, ) {} public static function fromEnvironment(): self { return new self( $_ENV['JWT_SECRET'] ?? 'secret', (int) ($_ENV['JWT_ACCESS_TOKEN_TTL'] ?? 900), (int) ($_ENV['SESSION_LIFETIME'] ?? 7200), (int) ($_ENV['AUTH_MAX_LOGIN_ATTEMPTS'] ?? 5), ); } } ``` ### User Entity ```php // src/Core/Auth/Domain/User.php final class User { public function __construct( private readonly UserId $id, private readonly Email $email, private HashedPassword $password, private bool $isActive = true, private int $failedLoginAttempts = 0, ) {} public static function create(UserId $id, Email $email, HashedPassword $password): self { return new self($id, $email, $password); } public function getId(): UserId { return $this->id; } public function getEmail(): Email { return $this->email; } public function verifyPassword(string $password): bool { return $this->password->verify($password); } public function isActive(): bool { return $this->isActive; } public function recordFailedLogin(): void { $this->failedLoginAttempts++; } public function resetLoginAttempts(): void { $this->failedLoginAttempts = 0; } } // src/Core/Auth/Domain/UserRepositoryInterface.php interface UserRepositoryInterface { public function save(User $user): void; public function findById(UserId $id): ?User; public function findByEmail(Email $email): ?User; public function emailExists(Email $email): bool; } ``` ### Authentication Services ```php // src/Core/Auth/AuthenticationManagerInterface.php interface AuthenticationManagerInterface { public function login(Email $email, string $password): AuthResult; public function register(Email $email, string $password): User; public function getCurrentUser(): ?User; public function isAuthenticated(): bool; } // src/Core/Auth/AuthResult.php final class AuthResult { public function __construct( public readonly bool $success, public readonly ?User $user = null, public readonly ?string $accessToken = null, public readonly ?string $error = null, ) {} public static function success(User $user, ?string $accessToken = null): self { return new self(true, $user, $accessToken); } public static function failure(string $error): self { return new self(false, null, null, $error); } } ``` ### Session Authentication Manager ```php // src/Core/Auth/SessionAuthenticationManager.php final class SessionAuthenticationManager implements AuthenticationManagerInterface { public function __construct( private readonly UserRepositoryInterface $userRepository, private readonly SessionManagerInterface $sessionManager, private readonly AuthConfig $authConfig, ) {} public function login(Email $email, string $password): AuthResult { $user = $this->userRepository->findByEmail($email); if (!$user || !$user->verifyPassword($password)) { return AuthResult::failure('Invalid credentials'); } $this->sessionManager->set('user_id', $user->getId()->value); return AuthResult::success($user); } public function register(Email $email, string $password): User { $user = User::create( UserId::fromInt(random_int(1, 999999)), $email, HashedPassword::fromPlainText($password) ); $this->userRepository->save($user); return $user; } public function getCurrentUser(): ?User { $userId = $this->sessionManager->get('user_id'); return $userId ? $this->userRepository->findById(UserId::fromInt($userId)) : null; } public function isAuthenticated(): bool { return $this->getCurrentUser() !== null; } } ``` ### JWT Authentication Manager ```php // src/Core/Auth/JwtAuthenticationManager.php final class JwtAuthenticationManager implements AuthenticationManagerInterface { private ?User $currentUser = null; public function __construct( private readonly UserRepositoryInterface $userRepository, private readonly JwtTokenManagerInterface $tokenManager, ) {} public function login(Email $email, string $password): AuthResult { $user = $this->userRepository->findByEmail($email); if (!$user || !$user->verifyPassword($password)) { return AuthResult::failure('Invalid credentials'); } $accessToken = $this->tokenManager->createAccessToken($user); return AuthResult::success($user, $accessToken); } public function register(Email $email, string $password): User { $user = User::create( UserId::fromInt(random_int(1, 999999)), $email, HashedPassword::fromPlainText($password) ); $this->userRepository->save($user); return $user; } public function getCurrentUser(): ?User { return $this->currentUser; } public function setCurrentUser(?User $user): void { $this->currentUser = $user; } public function isAuthenticated(): bool { return $this->currentUser !== null; } } ``` ### JWT Token Manager ```php // src/Core/Auth/JwtTokenManagerInterface.php interface JwtTokenManagerInterface { public function createAccessToken(User $user): string; public function validateAccessToken(string $token): ?array; } // src/Core/Auth/JwtTokenManager.php final class JwtTokenManager implements JwtTokenManagerInterface { public function __construct(private readonly AuthConfig $authConfig) {} public function createAccessToken(User $user): string { $payload = [ 'sub' => $user->getId()->value, 'email' => $user->getEmail()->value, 'exp' => time() + $this->authConfig->jwtAccessTokenTtl, ]; return JWT::encode($payload, $this->authConfig->jwtSecret, 'HS256'); } public function validateAccessToken(string $token): ?array { try { return (array) JWT::decode($token, new Key($this->authConfig->jwtSecret, 'HS256')); } catch (\Exception) { return null; } } } ``` ### Authentication Middleware ```php // src/Core/Auth/Middleware/RequireAuthMiddleware.php final class RequireAuthMiddleware implements MiddlewareInterface { public function __construct(private readonly AuthenticationManagerInterface $authManager) {} public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface { if (!$this->authManager->isAuthenticated()) { $response = new Response(); return $response->withStatus(401)->withHeader('Content-Type', 'application/json'); } return $handler->handle($request); } } ``` ### Route Protection Attributes ```php // src/Core/Auth/Attributes/RequireAuth.php #[Attribute(Attribute::TARGET_METHOD | Attribute::TARGET_CLASS)] final class RequireAuth {} // src/Core/Auth/Attributes/AllowAnonymous.php #[Attribute(Attribute::TARGET_METHOD | Attribute::TARGET_CLASS)] final class AllowAnonymous {} ``` ### Database Schema ```sql CREATE TABLE users ( id INT PRIMARY KEY AUTO_INCREMENT, email VARCHAR(255) UNIQUE NOT NULL, password VARCHAR(255) NOT NULL, is_active BOOLEAN DEFAULT TRUE, failed_login_attempts INT DEFAULT 0, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); ``` ### Repository Implementation ```php // src/Core/Auth/Infrastructure/Database/UserRepository.php final class UserRepository implements UserRepositoryInterface { public function __construct(private readonly PDO $pdo) {} public function save(User $user): void { $sql = "INSERT INTO users (id, email, password, is_active) VALUES (?, ?, ?, ?) ON DUPLICATE KEY UPDATE email=VALUES(email), password=VALUES(password)"; $this->pdo->prepare($sql)->execute([ $user->getId()->value, $user->getEmail()->value, $user->getPassword()->hash, $user->isActive() ]); } public function findById(UserId $id): ?User { $stmt = $this->pdo->prepare("SELECT * FROM users WHERE id = ?"); $stmt->execute([$id->value]); $data = $stmt->fetch(PDO::FETCH_ASSOC); return $data ? User::fromArray($data) : null; } public function findByEmail(Email $email): ?User { $stmt = $this->pdo->prepare("SELECT * FROM users WHERE email = ?"); $stmt->execute([$email->value]); $data = $stmt->fetch(PDO::FETCH_ASSOC); return $data ? User::fromArray($data) : null; } public function emailExists(Email $email): bool { $stmt = $this->pdo->prepare("SELECT COUNT(*) FROM users WHERE email = ?"); $stmt->execute([$email->value]); return $stmt->fetchColumn() > 0; } } ``` ### Bootstrapper Integration ```php // src/Core/Application/Bootstrapper/AuthenticationInitializer.php class AuthenticationInitializer implements BootstrapperInterface { public function bootstrap(InflectableContainer $container): void { $container->set(AuthConfig::class, AuthConfig::fromEnvironment()); $container->bind(UserRepositoryInterface::class, UserRepository::class, [PDO::class]); if (($_ENV['AUTH_TYPE'] ?? 'session') === 'jwt') { $container->bind(JwtTokenManagerInterface::class, JwtTokenManager::class, [AuthConfig::class]); $container->bind(AuthenticationManagerInterface::class, JwtAuthenticationManager::class); } else { $container->bind(AuthenticationManagerInterface::class, SessionAuthenticationManager::class); } } } ``` ### Usage Example ```php // Example controller class AuthController implements ControllerInterface { public function __construct(private readonly AuthenticationManagerInterface $authManager) {} #[Post('/login')] public function login(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface { $data = json_decode($request->getBody()->getContents(), true); $result = $this->authManager->login( Email::fromString($data['email']), $data['password'] ); $response->getBody()->write(json_encode($result->success ? ['success' => true, 'token' => $result->accessToken] : ['error' => $result->error] )); return $response->withHeader('Content-Type', 'application/json'); } #[Post('/register')] public function register(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface { $data = json_decode($request->getBody()->getContents(), true); try { $user = $this->authManager->register( Email::fromString($data['email']), $data['password'] ); $response->getBody()->write(json_encode(['success' => true])); } catch (\Exception $e) { $response->getBody()->write(json_encode(['error' => $e->getMessage()])); } return $response->withHeader('Content-Type', 'application/json'); } } ``` ## Environment Configuration ```env AUTH_TYPE=session JWT_SECRET=your-secret-key JWT_ACCESS_TOKEN_TTL=900 SESSION_LIFETIME=7200 AUTH_MAX_LOGIN_ATTEMPTS=5 ```