Docs

Auth

Stateless JWT authentication + attribute-driven authorization for any PSR-7/15 stack.

The package bundles three logical layers:

Layer

What it handles

Authentication

Login, password hashing/verification, JWT minting & refresh

Authorization

Policy classes, role/permission checks, per-route guards

Middleware

JwtAuthMiddleware (extracts/validates token) + AuthorizationMiddleware (enforces #[Can] rules)

It relies only on the official firebase/php-jwt helper—no heavyweight security frameworks.

1 · Installation

composer require monkeyscloud/monkeyslegion-auth

This pulls in firebase/php-jwt ^6.0 and autoloads everything under MonkeysLegion\Auth\.

2 · Password Hasher

use MonkeysLegion\Auth\PasswordHasher;

$hash = PasswordHasher::hash('super-secret');      // bcrypt by default
$isOk = PasswordHasher::verify('super-secret', $hash);
  • Uses password_hash() / password_verify() behind the scenes.

  • Algorithm & cost are tweakable via static setters (or DI config).

3 · JWT Service

use MonkeysLegion\Auth\JwtService;

$jwt = new JwtService(
    key:        $_ENV['JWT_SECRET'],
    issuer:     'monkeys.app',
    expiresIn:  3600           // seconds
);

$token = $jwt->issue(['sub' => $userId, 'role' => 'admin']);   // header.payload.signature
$claims = $jwt->parse($token);                                 // array ← verified & decoded

Symmetric by default; pass an RSA keypair if you prefer asymmetric signing.

Built on firebase/php-jwt.

4 · Login / AuthService

use MonkeysLegion\Auth\AuthService;

$auth = new AuthService($users, $hasher, $jwtService);

try {
    [$token, $claims] = $auth->attempt($email, $password);
} catch (AuthException $e) {
    // wrong creds → 401
}
  • $users is any repository implementing findByEmail() + findById().

  • attempt() returns [jwtString, claimsArray] on success.

5 · JWT Auth Middleware

use MonkeysLegion\Auth\JwtAuthMiddleware;

$kernel->add(new JwtAuthMiddleware(
    jwt:    $jwtService,
    header: 'Authorization',        // "Bearer <token>"
    cookie: 'ml_token',             // optional fallback
    passthrough: ['/login','/docs'] // no token required
));

If the token is missing or invalid → 401 JSON response.

On success, the decoded claims are injected as $request->getAttribute('user').

6 · Authorization (Policies + #[Can])

6.1 Defining a Policy

namespace App\Policies;

use App\Entity\Post;
use Psr\Http\Message\ServerRequestInterface;

final class PostPolicy implements PolicyInterface
{
    public function edit(ServerRequestInterface $req, Post $post): bool
    {
        $user = $req->getAttribute('user');
        return $user['id'] === $post->author_id || $user['role'] === 'admin';
    }
}

Register policies in the DI container or via a simple array:

$authz->register(Post::class, PostPolicy::class);

6.2 Guarding a Controller method

use MonkeysLegion\Auth\Attributes\Can;

#[Can('edit', App\Entity\Post::class)]
public function edit(ServerRequestInterface $req): ResponseInterface
{
    $post = $posts->find($req->getAttribute('route')['id']);
    // … handle edit
}

AuthorizationMiddleware inspects the attribute, calls the relevant policy

method, and—if it returns false—short-circuits with 403 Forbidden.

7 · Authorization Middleware

use MonkeysLegion\Auth\AuthorizationMiddleware;

$kernel->add(new AuthorizationMiddleware(
    service: $authz,                    // AuthorizationService instance
    onFail:  fn() => new JsonResponse(['error'=>'Forbidden'], 403),
));

Place it after JwtAuthMiddleware so user claims are available.

8 · Putting It All Together

$kernel = new MiddlewareDispatcher();

$kernel->add(new JwtAuthMiddleware($jwt));
$kernel->add(new AuthorizationMiddleware($authz));
$kernel->add($router->getMiddleware());          // routes with #[Can] guards

You now have secure login, stateless JWT sessions, and fine-grained

policy checks—all in ~40 lines of bootstrap code.

9 · CLI Goodies

# generate a new RSA keypair
php vendor/bin/ml auth:keygen --out config/jwt.pem --public-out public/jwt.pub

# hash a password for fixtures / tests
php vendor/bin/ml auth:hash "secret"

License

MIT © 2025 MonkeysCloud