ForgeAuth

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.

Overview

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.

What it gives you

  • Register / login / logout primitives
  • A pluggable user provider
  • Current-user context
  • Roles and permissions
  • JWT and API-key services
  • Route middleware / guards
  • Password rules & lockout settings

Prerequisite: ForgeAuth requires ForgeDatabaseSQL and ForgeSqlOrm, which it uses for its own tables (roles, permissions, API keys) and models.

Primitives, Not a Login

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.

Installation

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

The User Provider

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.

The Auth API

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.

The Current User

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()];
}

Roles & Permissions

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.

Middleware & Guards

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.

Configuration

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=/

Beyond ForgeAuth

ForgeAuth is the engine. What you build on it is your app. Common next steps:

  • A login/register UI and the routes that drive it.
  • Password reset and email-verification flows.
  • A concrete user model backed by your UserProvider.
  • An app-level auth business module that assembles these into a ready-to-use sign-in experience (built on top of ForgeAuth, kept separate from the engine).

Because ForgeAuth is a component, you can swap or extend it without the rest of your app knowing.