plin-code / laravel-platform-authorizer
Remote authorization for a vendor panel and signed feature flags in self-hosted Laravel installations
Package info
github.com/plin-code/laravel-platform-authorizer
pkg:composer/plin-code/laravel-platform-authorizer
Requires
- php: ^8.3
- ext-sodium: *
- illuminate/contracts: ^12.0||^13.0
- spatie/laravel-package-tools: ^1.16
Requires (Dev)
- larastan/larastan: ^3.0
- laravel/pennant: ^1.16
- laravel/pint: ^1.14
- livewire/livewire: ^4.0
- nunomaduro/collision: ^8.1.1
- orchestra/testbench: ^10.0||^11.0
- pestphp/pest: ^3.0||^4.0
- pestphp/pest-plugin-arch: ^3.0||^4.0
- pestphp/pest-plugin-laravel: ^3.0||^4.0
- phpstan/extension-installer: ^1.3
- phpstan/phpstan-deprecation-rules: ^2.0
- phpstan/phpstan-phpunit: ^2.0
Suggests
- laravel/pennant: Required to read and write feature flags through the signed driver
- livewire/livewire: Required to protect Livewire components of the panel
Provides
None
Conflicts
None
Replaces
None
This package is not auto-updated.
Last update: 2026-10-02 01:54:55 UTC
README
Laravel Platform Authorizer
Remote authorization for the vendor panel of a self-hosted Laravel application, and feature flags that only the vendor can change.
You sell a Laravel application that each customer runs on their own server, and you need an area inside it (for instance a panel that switches licensed features on and off) that the customer's staff cannot open, even though they own the server, the database and the .env file. This package closes that area behind a round trip to an authorizer run by the vendor, and keeps the feature flags in a manifest signed by the same authorizer.
- The application only holds Ed25519 public keys. It never holds a secret, so nothing on the customer's server can forge an authorization.
- The signed assertion in the session and the signed manifest in the database are verified again on every request. A row edited by hand grants nothing.
- Every failure closes the door with a generic refusal.
It does not stop someone who edits the source code. See How it works for the trust model and its limits.
Requirements
- PHP 8.3 or later with
ext-sodium - Laravel 12 or 13
laravel/pennantfor the flag driver (optional)livewire/livewirefor the component checks (optional)
Installation
composer require plin-code/laravel-platform-authorizer php artisan vendor:publish --tag=platform-authorizer-config php artisan vendor:publish --tag=platform-authorizer-migrations php artisan migrate
The migration creates the feature_manifests table, which keeps the signed manifest of the installation.
Quick Start
1. Configure
Ask the vendor for the authorizer URL, the product slug, the installation slug and the public keys. Give the vendor the callback URL of the installation (https://<your app>/platform-authorizer/callback) and the email of every local user who will open the panel: the assertion is only accepted for a logged in user with the same email.
Write the values in config/platform-authorizer.php, which belongs in version control. Only the installation slug comes from .env:
'url' => 'https://authorizer.example.com', 'product' => 'acme-crm', 'installation' => env('PLATFORM_INSTALLATION'), 'keys' => [ 'acme-2026-1' => 'G1yHmSCN25mB4Cp3WRutDKtJLT5ZXQdTGOAPmLeAA28=', ], 'protected_livewire_namespaces' => [ 'App\\Livewire\\Platform', ],
PLATFORM_INSTALLATION=acme-crm-rossi
Keep SESSION_LIFETIME at 15 minutes or more and the session cookie same_site at lax. See Configuration for every setting and the other gotchas.
2. Protect routes
use App\Livewire\Platform\PlatformPanel; use PlinCode\PlatformAuthorizer\Http\Middleware\RequirePlatformAuthorization; Route::middleware(['auth', RequirePlatformAuthorization::class]) ->prefix('platform') ->group(function () { Route::get('/', PlatformPanel::class)->name('platform'); });
Put auth before the middleware: the assertion is bound to the email of the logged in user. A navigation without a valid assertion goes through the authorizer and comes back where it was going.
3. Read and write flags
Register the store in config/pennant.php:
'default' => 'platform-authorizer', 'stores' => [ 'platform-authorizer' => ['driver' => 'platform-authorizer'], ],
Define each flag with its default, then read and write it as usual. Flags are global, and writes go through the authorizer, so they only work inside the protected area:
use Laravel\Pennant\Feature; Feature::define('check-in', fn () => false); // in a service provider, false unless the manifest says otherwise Feature::active('check-in'); Feature::for(config('platform-authorizer.global_scope'))->activate('check-in');
The package synchronises the flags every hour through the Laravel scheduler. Run php artisan platform-authorizer:sync-flags once after the first deploy.
Documentation
- How it works: the problem, the trust model and the limits.
- Configuration: what to exchange with the vendor, every setting with its default and validation, and the gotchas.
- Protecting routes: the middleware, the round trip routes, gates (with a Horizon example).
- Livewire: expired status and grace, protected components, a minimal panel.
- Feature flags: the Pennant store, defining, reading and writing flags, exceptions, synchronisation.
- Key rotation: replacing the signing key without downtime.
- Customisation: the refusal page, translations and locales, publish tags.
- Protocol: the endpoints, the token format and the claims.
- Troubleshooting: log messages, refusal reasons and the
AssertionRejectedevent. - Testing your application: the
FakeAuthorizerhelper.
Testing
composer test
Changelog
Please see CHANGELOG for more information on what has changed recently.
License
The MIT License (MIT). Please see License File for more information.
