Core

Middlewares & Security

Middlewares guard routes before main() runs. They execute in the order returned by middlewares(), using an onion-style pipeline — and Lunaris ships the security primitives you need alongside them.

The interface

Every middleware implements one method:

interface IMiddleware {
    public function handle($request, $next);
}

A complete middleware

<?php

namespace App\Middlewares;

use System\Routing\IMiddleware;

class LengthGuard implements IMiddleware {
    public function handle($request, $next) {
        $value = $request->get("length");

        if ((int)$value <= 20) {
            throw new Exception("Length is less than 21");
        }

        return $next($request); // pass to the next layer
    }
}
  • $request is the action instance itself, so you can inspect input, headers, $_FILES, etc.
  • Call $next($request) to continue along the pipeline.
  • To short-circuit a request, throw an exception (e.g. forbidden(), notfound()), return early, or emit a response directly.
  • Middleware classes are instantiated with plain new — there is no DI container, so constructor dependencies can’t be auto-injected.

Applying middlewares to an action

<?php

namespace App\Actions;

use System\Http\Action;
use App\Middlewares\IdsGuard;
use App\Middlewares\LengthGuard;

class AboutAction extends Action {
    public function middlewares() {
        return [
            IdsGuard::class,
            LengthGuard::class
        ];
    }

    public function main(array $params) {
        return response()->json([
            "params" => $params,
            "all"    => $this->all(),
        ]);
    }
}

How the pipeline works

System\Routing\MiddlewarePipeline composes the onion using array_reduce on the reversed middleware list, wrapping the destination closure (which calls main()):

$pipeline = new MiddlewarePipeline($middlewares);
$result = $pipeline->handle($instance, function($request) use ($instance, $params) {
    return $instance->main($params);
});
echo $result;

Built-in middleware: CsrfMiddleware

System\Http\Middlewares\CsrfMiddleware protects state-changing requests. On POST / PUT / PATCH / DELETE it validates a CSRF token sent either as $_POST['_token'] or the X-CSRF-TOKEN header, throwing ForbiddenException on failure.

<?php

namespace App\Actions;

use System\Http\Action;
use System\Http\Middlewares\CsrfMiddleware;

class SubmitAction extends Action {
    public function middlewares() {
        return [CsrfMiddleware::class];
    }

    public function main() {
        return response()->text("Saved!");
    }
}

Generate the hidden input for forms with the csrf_field() helper:

<form method="POST" action="<?= route("submit") ?>">
    <?= csrf_field() ?>
    <input type="text" name="name">
    <button>Submit</button>
</form>

Hashing passwords

System\Security\Hash wraps PHP’s password_* functions (bcrypt via PASSWORD_DEFAULT):

use System\Security\Hash;

$hash = Hash::make("secret-pass");     // bcrypt hash
Hash::verify("secret-pass", $hash);    // true
Hash::needsRehash($hash);              // false (rehash when true)

Encryption

System\Security\Crypt provides authenticated symmetric encryption via defuse/php-encryption (AES-256-GCM), keyed by the APP_KEY in your config:

use System\Security\Crypt;

$ciphertext = Crypt::encrypt("sensitive data");
$plaintext  = Crypt::decrypt($ciphertext);

The key is loaded lazily and cached. A missing APP_KEY throws an InvalidArgumentException.

CSRF tokens

Session-backed tokens validated with hash_equals (constant-time comparison):

use System\Routing\Csrf;

Csrf::generate();    // create/return the current token
Csrf::token();       // current token or null
Csrf::validate($t);  // true if it matches the session token
Csrf::regenerate();  // rotate the token
Csrf::destroy();     // remove the token

Hardening baked into the framework

ProtectionWhere
Session cookie hardeninghttponly, samesite=Lax, secure on HTTPS — set before session_start() in public/index.php.
Server file protectionRoot .htaccess denies web access to .env and *.log.
Error displaydisplay_errors=0, errors logged to storage/logs.
Host validationThe server() helper whitelists hostnames and only trusts a sane X-Forwarded-Proto.
Escape by defaultFlash::render(..., $escape = true) escapes messages; Str::sanitize() for ad-hoc values.
ValidationThe Validateable trait — see Validation & Sessions.

Part of the Lunaris documentation · Source on Codeberg · MIT License