📦 Marketplace⭐ GitHub
Auth & Securityv2.1

Auth

Multi-guard, attribute-first authentication and authorization for the MonkeysLegion framework. Ground-up rebuild for PHP 8.4 with property hooks, typed constants, and zero hard dependencies.

Features

FeatureStatus
Multi-Guard SystemJWT, Session, API Key, WebAuthn/Passkey, Composite (try multiple in order)
Attribute-First Auth#[Guard], #[Authenticated], #[Authorize], #[RequiresRole], #[RequiresPermission], #[RateLimit], #[Passkey]
JWT ServiceHS256/RS256, token families, refresh rotation attack detection
Session GuardSession fixation prevention, token version validation, remember-me
Policy Gateallows(), denies(), authorize(), inspect() with deny reasons
RBACHierarchical roles, wildcard permissions, super-admin, decoupled via RoleRepositoryInterface
Password HasherNIST SP 800-63B policy engine, Argon2ID/bcrypt, auto-rehash
Rate LimitingPer-route, per-user, configurable via attributes
Two-Factor AuthTOTP (RFC 6238), backup/recovery codes
PSR-15 MiddlewareAuthentication, Authorization, Rate Limiting — all attribute-aware
PHP 8.4 NativeProperty hooks, typed constants, readonly DTOs

Requirements

  • PHP 8.4 or higher
  • firebase/php-jwt ^7.0
  • psr/http-message ^2.0

Installation

composer require monkeyscloud/monkeyslegion-auth:dev-2.0.0

Architecture

The package is organized into several clear namespaces:

  • Attribute/: Route and controller attributes (#[RequiresRole], #[RateLimit], etc.)
  • Guard/: Authentication implementations (JwtGuard, SessionGuard, ApiKeyGuard)
  • Middleware/: PSR-15 middlewares for Auth and Rate Limiting
  • Service/: Core logic (AuthService, JwtService, PasswordHasher)
  • Policy/: Authorization gate (Gate)
  • RBAC/: Role-Based Access Control logic (RbacService)
  • TwoFactor/: 2FA implementations (TotpProvider)

Authentication Setup

1. Setting up the AuthManager and Guards

The AuthManager acts as a registry for your guards.

use MonkeysLegion\Auth\Guard\AuthManager;
use MonkeysLegion\Auth\Guard\JwtGuard;
use MonkeysLegion\Auth\Service\JwtService;

// Setup JWT Service
$jwtService = new JwtService('your-secret-key-at-least-32-chars');

// Register Guard
$jwtGuard = new JwtGuard($jwtService, $userProvider);
$manager = new AuthManager(defaultGuard: 'jwt');
$manager->register('jwt', $jwtGuard);

2. Basic Login using AuthService

The AuthService orchestrates credentials checking, hashing, rate limiting, and token issuance.

use MonkeysLegion\Auth\Service\AuthService;
use MonkeysLegion\Auth\Exception\InvalidCredentialsException;

$authService = new AuthService($userProvider, $passwordHasher, $jwtService, $tokenStorage, $rateLimiter);

try {
    $result = $authService->login('user@example.com', 'password123');
    
    if ($result->requires2FA) {
        // Handle Two-Factor Authentication flow
        return ['challenge_token' => $result->challengeToken];
    }
    
    return [
        'user' => $result->user->toArray(),
        'tokens' => $result->tokens->toArray(), // Returns TokenPair with access & refresh tokens
    ];
} catch (InvalidCredentialsException $e) {
    return response('Unauthorized', 401);
}

Guard Implementations

Session Guard (Stateful)

Ideal for traditional web applications. Protects against session fixation and supports "remember me".

use MonkeysLegion\Auth\Guard\SessionGuard;

$guard = new SessionGuard($session, $userProvider);

// Perform login
$guard->login($user, remember: true);

// Authenticate from current request/session
$user = $guard->authenticate($request);

// Logout
$guard->logout();

API Key Guard (Stateless)

Validates API keys via headers (e.g. X-API-Key) or query parameters.

use MonkeysLegion\Auth\Guard\ApiKeyGuard;

$guard = new ApiKeyGuard($userProvider, headerName: 'X-API-Key');
$manager->register('api', $guard);

// Reads X-API-Key from $request
$user = $guard->authenticate($request);

Composite Guard

Tries multiple guards in sequence. First match wins.

use MonkeysLegion\Auth\Guard\CompositeGuard;

$composite = new CompositeGuard([$jwtGuard, $apiKeyGuard]);
$manager->register('mixed', $composite);

// Will try JWT first, then API Key
$user = $composite->authenticate($request);

Attribute-Based Security

MonkeysLegion v2 uses PHP 8 attributes extensively to secure your controllers and routes. These attributes are automatically parsed by the AuthenticationMiddleware and AuthorizationMiddleware.

use MonkeysLegion\Auth\Attribute\Authenticated;
use MonkeysLegion\Auth\Attribute\RequiresRole;
use MonkeysLegion\Auth\Attribute\RequiresPermission;
use MonkeysLegion\Auth\Attribute\RateLimit;

#[Authenticated(guard: 'jwt')]
#[RequiresRole(['admin', 'editor'], mode: 'any')]
#[RateLimit(maxAttempts: 60, decaySeconds: 60)]
class ArticleController
{
    #[RequiresPermission('articles.publish')]
    public function publish(int $id): Response
    {
        // Only accessible if:
        // 1. Authenticated via JWT
        // 2. User has 'admin' OR 'editor' role
        // 3. User has 'articles.publish' permission
        // 4. Rate limit is not exceeded
    }
}

Role-Based Access Control (RBAC)

The RbacService supports exact matches and wildcard permissions (e.g. posts.*), as well as super-admin overrides (*).

use MonkeysLegion\Auth\RBAC\RbacService;

$rbac = new RbacService($roleRepository);

// Check Roles
$rbac->hasRole($user, 'admin');
$rbac->hasAnyRole($user, ['admin', 'editor']);

// Check Permissions
$rbac->hasPermission($user, 'posts.edit');

Policy Gate (Authorization)

The Gate allows you to define fine-grained ability checks or delegate them to Policy classes.

use MonkeysLegion\Auth\Policy\Gate;

$gate = new Gate();

// Define an ability using a closure
$gate->define('update-post', fn($user, $post) => $user->id === $post->authorId);

// Or map a Model to a Policy class
$gate->policy(Post::class, PostPolicy::class);

// Perform Checks
if ($gate->allows($user, 'update-post', $post)) {
    // allowed
}

// Check with detailed inspection
$inspection = $gate->inspect($user, 'update-post', $post);
if (!$inspection['allowed']) {
    echo $inspection['reason']; // e.g. "Allowed by before callback." or "Denied by ability definition."
}

// Authorize will throw an UnauthorizedException if denied
$gate->authorize($user, 'update-post', $post);

Advanced Features

Token Family Rotation (JWT)

The JwtService and AuthService implement token family rotation to mitigate refresh token theft. If a blacklisted refresh token is reused, the entire token family is compromised. AuthService will automatically increment the user's token version and revoke all tokens.

// Automatically validates refresh token, checks blacklist, rotates family, and returns new TokenPair
$tokens = $authService->refresh($refreshToken, $requestIp);

Password Hasher and Policies

The PasswordHasher enforces NIST SP 800-63B guidelines via the PasswordPolicy DTO.

use MonkeysLegion\Auth\Service\PasswordHasher;
use MonkeysLegion\Auth\DTO\PasswordPolicy;

$hasher = new PasswordHasher(
    policy: new PasswordPolicy(
        minLength: 12,
        requireUppercase: true,
        requireNumbers: true,
        requireSymbols: true,
        rejectCommon: true, // Prevents "password123", etc.
    )
);

// Will throw InvalidArgumentException if the policy is not met
$hash = $hasher->hash('MyStr0ng!Pass');

// Verify and check if rehash is needed (e.g. cost factor increased)
if ($hasher->verify('MyStr0ng!Pass', $hash)) {
    if ($hasher->needsRehash($hash)) {
        // Upgrade hash transparently
        $newHash = $hasher->hashWithoutPolicy('MyStr0ng!Pass');
    }
}

Rate Limiting

The InMemoryRateLimiter provides fixed-window rate limiting. AuthService uses it out of the box to prevent brute force attacks on the login and register endpoints.

use MonkeysLegion\Auth\RateLimit\InMemoryRateLimiter;

$limiter = new InMemoryRateLimiter();
$limiter->attempt('login:user@example.com', maxAttempts: 5, decaySeconds: 900); // true/false

Two-Factor Authentication (TOTP)

use MonkeysLegion\Auth\TwoFactor\TotpProvider;

$totp = new TotpProvider();

// Generate a new secret for a user
$secret = $totp->generateSecret();

// Get URI for QR Code generation (Google Authenticator, Authy)
$uri = $totp->getProvisioningUri($secret, 'user@example.com', 'My App');

// Verify a code submitted by the user
$isValid = $totp->verify($secret, '123456');

Security Posture

  • Token family tracking — detects refresh token reuse attacks
  • CSPRNG token IDsrandom_bytes(16) for all token identifiers
  • Session fixation prevention — session regenerated on every login
  • Token versioning — increment version to invalidate all sessions/tokens globally
  • Timing-safe comparisonshash_equals for all credential/token checks
  • Account lockout — configurable failed attempt limits
  • Audit trail — all auth events include correlation IDs

Testing

composer test
composer phpstan

License

MIT © MonkeysCloud