The authentication engine your app builds on: register, log in, log out, know who the current user is, and gate features with roles and permissions. It's the primitives behind the scenes — not a pre-built login page. You decide how your app presents those flows.
ForgeAuth is a component that supplies the mechanics of authentication and authorization to your app. It handles credential verification, session establishment, and the role/permission model — then hands control back to you. Your app (or one of your business modules) decides whether registration and login are a web form, an API endpoint, a CLI command, or all of the above.
Prerequisite: ForgeAuth requires ForgeDatabaseSQL and ForgeSqlOrm, which it uses for its own tables (roles, permissions, API keys) and models.
It's worth being explicit: ForgeAuth is the engine, not a finished login experience. The core contract it provides is deliberately small — register, authenticate, and forget. The business layer — what your signup page collects, how login errors look, whether you use JWT or sessions — is something you or a business module implements on top.
In this documentation, the concrete business-layer implementations built on ForgeAuth (such as the app-level auth modules that give you real register/login/forgot/reset flows) are kept separate for that reason.
php forge.php package:install-module --module=ForgeAuth
Installing runs the module's migrations
(db:migrate --type=module --module=ForgeAuth)
automatically, so its tables are in place.
# Manage roles and users from the CLI
modules:auth:role:create modules:auth:role:add-permission
modules:auth:role:remove-permission modules:auth:role:delete
modules:auth:user:add modules:auth:user:assign-role
ForgeAuth doesn't assume where your users live. It
defines a UserProvider contract, and
your app supplies the implementation — finding a
user by id, identifier, or email; verifying
credentials; creating a user; and paginating them.
This is the seam where your user model
meets the auth engine.
use Modules\ForgeAuth\Contracts\UserProviderInterface;
use Modules\ForgeAuth\Contracts\AuthUserInterface;
final class AppUserProvider implements UserProviderInterface
{
public function findById(int $id): ?AuthUserInterface { /* ... */ }
public function findByEmail(string $email): ?AuthUserInterface { /* ... */ }
public function verifyCredentials(string $identifier, string $password): ?AuthUserInterface { /* ... */ }
public function createUser(array $credentials): AuthUserInterface { /* ... */ }
public function paginate(int $page = 1, int $perPage = 10, array $options = []): Paginator { /* ... */ }
}
Whatever represents a user in your app implements
AuthUserInterface — an id, an
identifier, and an email.
In your app's authentication code, work with the
ForgeAuthInterface from the container.
It's a small, focused surface:
use Modules\ForgeAuth\Contracts\ForgeAuthInterface;
// In a class the container builds for you
public function __construct(
private ForgeAuthInterface $auth,
) {
}
public function signIn(array $credentials): mixed
{
// verify and establish a session for the authenticated user
$user = $this->auth->login($credentials);
return $user; // an AuthUserInterface
}
public function signOut(): void
{
$this->auth->logout();
}
register(), login(), and
logout() are the primitives. How those
map to your routes, forms, and responses is up to
your app.
Once authenticated, your code needs to know who's making the call. Use the current-user context instead of threading the user through every method:
use Modules\ForgeAuth\Contracts\UserContextInterface;
public function index(UserContextInterface $context): mixed
{
if (! $context->isAuthenticated()) {
return redirect('/login');
}
$user = $context->current(); // ?AuthUserInterface
return ['email' => $user?->getEmail()];
}
ForgeAuth ships a role and permission model. Define roles and permissions as enums, then check access in your code with the included helpers:
use Modules\ForgeAuth\Enums\Role;
use Modules\ForgeAuth\Enums\Permission;
// In a controller or service
if (hasRole(Role::ADMIN)) {
// admin-only behavior
}
if (can(Permission::USER_WRITE)) {
// allowed
}
if (cannot([Permission::USER_DELETE])) {
return abort(403);
}
Helpers include can(),
canAny(), canAll(),
cannot(), hasRole(),
hasRoleEnum(),
getAllUserPermissions(), and
isOwner() — the last of which lets a
creator also act on a resource regardless of role.
For class/method-level checks, compose the included
traits (HasRoles,
HasCurrentUser) and use the
RequiresPermission attribute. Roles and
permissions can be managed via the
role: CLI commands.
To protect routes, ForgeAuth provides middleware you can mount on your routes:
PermissionMiddleware — require a
permission to proceed.
RoleMiddleware — require a role.
ApiJwtMiddleware — authenticate an
API call with a JWT.
ApiKeyMiddleware — authenticate an
API call with an API key.
Stateless API auth is backed by
JwtService (encode /
decode) and the API-key service,
giving you the choice of session-based or token-based
auth in your app.
Behavior is tuned with environment variables, all optional with sensible defaults:
# JWT
FORGE_JWT_ENABLED=false
FORGE_JWT_SECRET=your-secure-jwt-secret
FORGE_JWT_TTL=900
FORGE_JWT_REFRESH_TTL=604800
# Password rules & lockout
FORGE_PASSWORD_COST=12
FORGE_MAX_LOGIN_ATTEMPTS=3
FORGE_LOCKOUT_TIME=300
FORGE_MIN_PASSWORD_LENGTH=6
FORGE_MAX_PASSWORD_LENGTH=256
# Redirect targets (used by your login/logout flow)
FORGE_AFTER_LOGIN_REDIRECT=/
FORGE_AFTER_LOGOUT_REDIRECT=/
ForgeAuth is the engine. What you build on it is your app. Common next steps:
UserProvider.
Because ForgeAuth is a component, you can swap or extend it without the rest of your app knowing.