Complete authentication system for Forge Kernel with session-based web authentication and optional JWT support for mobile and API applications.
ForgeAuth provides a complete, production-ready authentication system for Forge Kernel applications. It offers session-based authentication for traditional web applications and optional JWT (JSON Web Token) support for mobile apps and API clients. Built with simplicity, robustness, and flexibility in mind.
AuthMiddleware for web, ApiJwtMiddleware for API.Simple Yet Powerful: ForgeAuth is designed to be simple to use while providing robust security features. Web authentication works out of the box with no configuration. JWT support is available when you need it, but it's opt-in so you're not forced to use it.
ForgeAuth is built with a clear separation of concerns and a focus on simplicity without sacrificing power or security.
ForgeAuth follows a service-based architecture that separates authentication logic from controllers and routes. This design provides:
ForgeAuth clearly separates web and API authentication:
Session-based, works out of the box
AuthMiddleware for protectionWebLoginController for routesJWT-based, opt-in via configuration
ApiJwtMiddleware for protectionApiLoginController for routesJWT support is opt-in, not enabled by default. This means:
Design Decision: By making JWT opt-in, ForgeAuth stays simple for web applications while still providing powerful mobile/API capabilities when needed. You're not forced to configure JWT if you only need web authentication.
User data access uses the repository pattern with built-in caching:
Install ForgeAuth using ForgePackageManager. The module includes database migrations that will run automatically after installation.
# Install with wizard (interactive)
php forge.php package:install-module
# Install directly (skip wizard)
php forge.php package:install-module --module=ForgeAuth
# Install specific version
php forge.php package:install-module --module=ForgeAuth@0.2.4
After installation, ForgeAuth automatically runs database migrations to create the necessary tables:
users table for user accountsprofiles table for user profiles (optional)
Automatic Migrations: The module uses #[PostInstall] attribute to automatically run migrations after installation. No manual steps required.
Verify that ForgeAuth is installed correctly:
# List installed modules
php forge.php package:list-modules | grep ForgeAuth
# Check if tables were created
php forge.php db:migrate:status
ForgeAuth requires minimal configuration. Web authentication works out of the box. JWT support is opt-in and requires additional configuration.
Web authentication using PHP sessions requires no configuration. It works immediately after installation. The session configuration is handled by Forge Kernel's session system.
To enable JWT support for mobile applications or API clients, add the following to your config/security.php or .env file:
// config/security.php
return [
'jwt' => [
'enabled' => true, // Enable JWT support
'secret' => env('JWT_SECRET', 'your-secret-key-here'), // Required if enabled
'ttl' => 900, // Access token TTL in seconds (default: 15 minutes)
'refresh_ttl' => 604800, // Refresh token TTL in seconds (default: 7 days)
],
];
# .env file
JWT_SECRET=your-very-secure-secret-key-here-minimum-32-characters
security.jwt.enabled (default: false) — Enable or disable JWT supportsecurity.jwt.secret (required if enabled) — Secret key for signing JWT tokens. Use a strong, random string.security.jwt.ttl (default: 900) — Access token time-to-live in seconds (15 minutes)security.jwt.refresh_ttl (default: 604800) — Refresh token time-to-live in seconds (7 days)Security Warning: If you enable JWT, you must set a strong secret key. Use a random string of at least 32 characters. Never commit secrets to version control — use environment variables.
ForgeAuth includes built-in login attempt throttling to prevent brute-force attacks. Configure it in config/security.php:
// config/security.php
return [
'password' => [
'max_login_attempts' => 5, // Maximum failed login attempts
'lockout_time' => 300, // Lockout duration in seconds (default: 5 minutes)
],
];
security.password.max_login_attempts (default: 5) — Maximum failed login attempts before lockoutsecurity.password.lockout_time (default: 300) — Lockout duration in seconds (5 minutes)Session-based authentication for traditional web applications. Works out of the box with no JWT configuration required.
Web authentication uses PHP sessions:
ForgeAuthService::login() validates credentials
The ForgeAuthService provides the main authentication methods:
use App\Modules\ForgeAuth\Services\ForgeAuthService;
class MyController
{
public function __construct(
private readonly ForgeAuthService $authService
) {}
public function login(array $credentials): Response
{
// Login user (throws LoginException on failure)
$user = $this->authService->login($credentials);
// Get current authenticated user
$user = $this->authService->user();
// Logout user
$this->authService->logout();
// Register new user
$this->authService->register($credentials);
}
}
Use AuthMiddleware to protect routes that require authentication:
use App\Modules\ForgeAuth\Middlewares\AuthMiddleware;
use Forge\Core\Http\Attributes\Middleware;
use Forge\Core\Routing\Route;
#[Middleware("web")]
final class DashboardController
{
#[Route("/dashboard")]
#[Middleware("App\Modules\ForgeAuth\Middlewares\AuthMiddleware")]
public function welcome(): Response
{
// This route requires authentication
// Users will be redirected to /auth/login if not authenticated
$user = $this->authService->user();
return $this->view("pages/dashboard/index", [
"user" => $user,
]);
}
}
Here's a complete example of user registration from HomeController:
use App\Modules\ForgeAuth\Services\ForgeAuthService;
use App\Modules\ForgeAuth\Validation\ForgeAuthValidate;
use Forge\Core\Helpers\Flash;
use Forge\Core\Helpers\Redirect;
use Forge\Exceptions\ValidationException;
#[Route("/", "POST")]
public function register(Request $request): Response
{
try {
// Validate input
ForgeAuthValidate::register($request->postData);
// Sanitize input
$credentials = $this->sanitize($request->postData);
// Register user
$this->forgeAuthService->register($credentials);
Flash::set("success", "User registered successfully");
return Redirect::to("/");
} catch (ValidationException) {
return Redirect::to("/");
}
}
ForgeAuth automatically throttles login attempts to prevent brute-force attacks:
max_login_attempts failed attempts, the user is locked outlockout_time seconds
Automatic Protection: Login throttling is built into ForgeAuthService::login(). No additional configuration needed — it works automatically.
Optional JWT support for mobile applications and API clients. JWT is opt-in — enable it in configuration when you need it.
First, enable JWT in your configuration (see Configuration section). Once enabled, you can use JWT authentication for API routes and mobile applications.
JWT authentication uses access tokens and refresh tokens:
Authorization: Bearer <token> headerHere's how API login works with JWT tokens:
use App\Modules\ForgeAuth\Services\ForgeAuthService;
use App\Modules\ForgeAuth\Validation\ForgeAuthValidate;
#[ApiRoute('/auth/login', 'POST')]
public function login(Request $request): Response
{
try {
$data = $request->json() ?: $request->postData;
ForgeAuthValidate::login($data);
$loginCredentials = $this->sanitize($data);
// Login user (creates session for web, but we'll use JWT)
$user = $this->forgeAuthService->login($loginCredentials);
// Issue JWT tokens
$tokens = $this->forgeAuthService->issueToken($user);
// Returns: {
// "user": {...},
// "tokens": {
// "access_token": "...",
// "refresh_token": "...",
// "expires_in": 900
// }
// }
return $this->apiResponse([
'user' => $user,
'tokens' => $tokens,
]);
} catch (LoginException $e) {
return $this->apiError('Invalid credentials', 401);
} catch (\RuntimeException $e) {
return $this->apiError('JWT is not enabled', 500);
}
}
When the access token expires, use the refresh token to get new tokens:
#[ApiRoute('/auth/refresh', 'POST')]
public function refresh(Request $request): Response
{
$data = $request->json() ?: $request->postData;
$refreshToken = $data['refresh_token'] ?? null;
if (!$refreshToken) {
return $this->apiError('Refresh token is required', 400);
}
$tokens = $this->forgeAuthService->refreshToken($refreshToken);
if (!$tokens) {
return $this->apiError('Invalid refresh token', 401);
}
// Returns new access_token and refresh_token
return $this->apiResponse($tokens);
}
Use ApiJwtMiddleware to protect API routes:
use App\Modules\ForgeAuth\Middlewares\ApiJwtMiddleware;
use Forge\Core\Http\Attributes\ApiRoute;
use Forge\Core\Http\Attributes\Middleware;
#[Middleware('api')]
final class ApiUserController
{
#[ApiRoute('/users', 'GET')]
#[Middleware("App\Modules\ForgeAuth\Middlewares\ApiJwtMiddleware")]
public function index(Request $request): Response
{
// This route requires valid JWT token
// Token is validated and user is loaded automatically
$user = $this->authService->user();
return $this->apiResponse($user);
}
}
You can add custom claims to JWT tokens using callbacks:
use App\Modules\ForgeAuth\Services\ForgeAuthService;
// Add custom claims callback
ForgeAuthService::addCustomClaimsCallback(function ($user, $basePayload) {
return [
'role' => $user->role,
'permissions' => $user->permissions,
// Add any custom data you need
];
});
// Now when tokens are issued, they'll include your custom claims
$tokens = $authService->issueToken($user);
Protected Claims: The following claims are protected and cannot be overridden: user_id, exp, iat, jti, type.
ForgeAuth provides several services for authentication and user management.
The main authentication service providing core authentication methods:
interface ForgeAuthInterface
{
// Web authentication
public function register(array $credentials): bool;
public function login(array $credentials): User;
public function logout(): void;
public function user(): ?User;
// JWT methods (available when JWT is enabled)
public function issueToken(User $user): array;
public function refreshToken(string $refreshToken): ?array;
public function resolveUserFromToken(string $token): ?User;
}
register($credentials) — Register a new user. Throws UserRegistrationException on failure.login($credentials) — Authenticate user and create session. Throws LoginException on failure.logout() — Clear user session.user() — Get current authenticated user from session or JWT token.issueToken($user) — Issue JWT access and refresh tokens. Requires JWT to be enabled.refreshToken($refreshToken) — Refresh access token using refresh token.resolveUserFromToken($token) — Resolve user from JWT token. Used by middleware.Handles JWT encoding and decoding:
class JwtService
{
public function encode(array $payload): string;
public function decode(string $token): array;
}
JwtTokenInvalidException for invalid tokensJwtTokenExpiredException for expired tokensRepository for user data access with built-in caching:
interface UserRepositoryInterface
{
public function create(CreateUserData $data): User;
public function findById(int $id): ?User;
public function findByIdentifier(string $identifier): ?User;
public function findByEmail(string $email): ?User;
}
UserRepositoryInterface for testabilityForgeAuth provides two middleware classes for protecting routes.
Protects web routes using session-based authentication:
use App\Modules\ForgeAuth\Middlewares\AuthMiddleware;
#[Route("/dashboard")]
#[Middleware("App\Modules\ForgeAuth\Middlewares\AuthMiddleware")]
public function dashboard(): Response
{
// User is authenticated via session
$user = $this->authService->user();
return $this->view("dashboard", ["user" => $user]);
}
/auth/login if not authenticatedProtects API routes using JWT tokens:
use App\Modules\ForgeAuth\Middlewares\ApiJwtMiddleware;
#[ApiRoute("/api/users", "GET")]
#[Middleware("App\Modules\ForgeAuth\Middlewares\ApiJwtMiddleware")]
public function getUsers(): Response
{
// User is authenticated via JWT token
$user = $this->authService->user();
return $this->apiResponse($user);
}
Authorization: Bearer <token> header
Automatic User Resolution: Both middleware classes automatically resolve the authenticated user and make it available via $authService->user().
ForgeAuth provides models and DTOs for user data management.
The User model represents authenticated users:
#[Table("users")]
#[ProtectedFields(['password'])]
class User extends Model
{
use HasTimeStamps;
use CanLoadRelations;
public int $id;
public string $status; // 'active', 'inactive', 'pending'
public string $identifier; // Unique username
public string $email; // Unique email
public string $password; // Hashed password (protected)
public ?UserMetadataDto $metadata; // JSON metadata
#[Relate(RelationKind::HasOne, Profile::class, "user_id")]
public function profile(): Relation;
}
Optional profile model for extended user information:
#[Table("profiles")]
class Profile extends Model
{
public int $id;
public int $userId;
public string $firstName;
public ?string $lastName;
public ?string $avatar;
public ?string $email;
public ?string $phone;
// ... other fields
}
ForgeAuth uses DTOs for type-safe data handling:
CreateUserData — For user registrationUserDto — For user data transferProfileDto — For profile dataUserMetadataDto — For user metadataUserNotificationDto — For notification preferencesForgeAuth provides validation helpers for login and registration.
Static validation methods for authentication forms:
use App\Modules\ForgeAuth\Validation\ForgeAuthValidate;
// Validate login credentials
ForgeAuthValidate::login([
'identifier' => 'username',
'password' => 'password123'
]);
// Validate registration data
ForgeAuthValidate::register([
'identifier' => 'username',
'email' => 'user@example.com',
'password' => 'password123'
]);
identifier — Requiredpassword — Requiredidentifier — Required, minimum 3 characters, unique in users tablepassword — Required, minimum 8 characters// Custom validation messages
ForgeAuthValidate::register($data, [
'required' => 'The :field field is required!',
'min' => 'The :field field must be at least :value characters.',
'unique' => 'The :field is already taken.'
]);
ForgeAuth includes ready-to-use controllers for authentication and user management.
Handles web login and logout routes:
GET /auth/login — Show login formPOST /auth/login — Process loginPOST /auth/logout — Logout userHandles API login and token refresh:
POST /api/auth/login — Login and get JWT tokensPOST /api/auth/refresh — Refresh access tokenWeb routes for user management:
GET /users — List users (with pagination)GET /users/{id} — Get user by IDGET /users/export — Export users to CSVAPI routes for user management:
GET /api/users — List users (with pagination)GET /api/users/{id} — Get user by IDGET /api/users/export — Export users to CSVCustomization: You can extend or replace these controllers with your own implementations. They're provided as examples and starting points.
ForgeAuth includes database migrations that run automatically after installation.
The users table stores user accounts:
// Migration: CreateUsersTable
#[Table(name: "users")]
#[Timestamps]
#[SoftDelete]
#[MetaData]
class CreateUsersTable extends Migration
{
public int $id; // Primary key
public string $status; // ENUM: 'active', 'inactive', 'pending'
public string $identifier; // Unique username
public string $email; // Unique email
public string $password; // Hashed password
}
created_at and updated_at timestampsdeleted_at)id, email, and deleted_at
The profiles table stores extended user information:
// Migration: CreateProfilesTable
#[Table(name: "profiles")]
#[Timestamps]
#[SoftDelete]
#[MetaData]
class CreateProfilesTable extends Migration
{
public int $id;
public int $userId; // Foreign key to users
public string $firstName;
public ?string $lastName;
public ?string $avatar;
public ?string $email;
public ?string $phone;
// ... other fields
}
ForgeAuth includes tenant-specific migrations for multi-tenant applications:
Tenants/CreateTenantUsersTable.php — Tenant-specific users tableComplete examples of using ForgeAuth in your controllers.
use App\Modules\ForgeAuth\Services\ForgeAuthService;
use App\Modules\ForgeAuth\Validation\ForgeAuthValidate;
use Forge\Core\Helpers\Flash;
use Forge\Core\Helpers\Redirect;
use Forge\Exceptions\ValidationException;
#[Route("/", "POST")]
public function register(Request $request): Response
{
try {
// Validate input
ForgeAuthValidate::register($request->postData);
// Sanitize input
$credentials = $this->sanitize($request->postData);
// Register user
$this->forgeAuthService->register($credentials);
Flash::set("success", "User registered successfully");
return Redirect::to("/");
} catch (ValidationException) {
return Redirect::to("/");
}
}
use App\Modules\ForgeAuth\Services\ForgeAuthService;
use App\Modules\ForgeAuth\Middlewares\AuthMiddleware;
use Forge\Core\Http\Attributes\Middleware;
use Forge\Core\Routing\Route;
#[Service]
#[Middleware("web")]
final class DashboardController
{
public function __construct(
private readonly ForgeAuthService $authService
) {}
#[Route("/dashboard")]
#[Middleware("App\Modules\ForgeAuth\Middlewares\AuthMiddleware")]
public function welcome(): Response
{
$data = [
"title" => "Welcome to Dashboard",
"user" => $this->authService->user() ?? [],
];
return $this->view(view: "pages/dashboard/index", data: $data);
}
}
use App\Modules\ForgeAuth\Services\ForgeAuthService;
use App\Modules\ForgeAuth\Middlewares\ApiJwtMiddleware;
use Forge\Core\Http\Attributes\ApiRoute;
use Forge\Core\Http\Attributes\Middleware;
#[Middleware('api')]
final class ApiUserController
{
public function __construct(
private readonly ForgeAuthService $authService
) {}
#[ApiRoute('/api/users/me', 'GET')]
#[Middleware("App\Modules\ForgeAuth\Middlewares\ApiJwtMiddleware")]
public function getCurrentUser(): Response
{
// User is automatically loaded from JWT token
$user = $this->authService->user();
return $this->apiResponse([
'user' => $user,
]);
}
}
Example of how mobile apps would use JWT tokens:
// Login and get tokens
const response = await fetch('/api/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
identifier: 'username',
password: 'password123'
})
});
const { user, tokens } = await response.json();
// Store tokens securely
localStorage.setItem('access_token', tokens.access_token);
localStorage.setItem('refresh_token', tokens.refresh_token);
// Use access token for API requests
const apiResponse = await fetch('/api/users/me', {
headers: {
'Authorization': `Bearer ${tokens.access_token}`
}
});
// Refresh token when access token expires
const refreshResponse = await fetch('/api/auth/refresh', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
refresh_token: tokens.refresh_token
})
});
const newTokens = await refreshResponse.json();
ForgeAuth provides opt-in JWT support for mobile applications. JWT is not enabled by default — you enable it when you need it.
JWT support is opt-in because:
To enable JWT support for mobile applications:
security.jwt.enabled = true in configurationsecurity.jwt.secret keyApiLoginController for login endpointsApiJwtMiddleware to protect API routesMobile apps authenticate using this flow:
POST /api/auth/loginAuthorization: Bearer <token> header for API requestsAccess tokens are short-lived (default: 15 minutes). Use refresh tokens to get new access tokens:
// When access token expires (401 response)
async function refreshAccessToken() {
const refreshToken = localStorage.getItem('refresh_token');
const response = await fetch('/api/auth/refresh', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ refresh_token: refreshToken })
});
if (response.ok) {
const tokens = await response.json();
localStorage.setItem('access_token', tokens.access_token);
localStorage.setItem('refresh_token', tokens.refresh_token);
return tokens.access_token;
} else {
// Refresh token expired, user must login again
redirectToLogin();
}
}
Add custom claims to JWT tokens for mobile-specific data:
use App\Modules\ForgeAuth\Services\ForgeAuthService;
// Add custom claims callback
ForgeAuthService::addCustomClaimsCallback(function ($user, $basePayload) {
return [
'role' => $user->role,
'permissions' => $user->permissions,
'subscription_tier' => $user->subscriptionTier,
// Any data your mobile app needs
];
});
Mobile-First Design: JWT tokens are designed for stateless authentication, perfect for mobile apps that need to work offline or with intermittent connectivity.
Recommendations for using ForgeAuth effectively and securely.