rappasoft/vault
Rappasoft Vault gives Laravel apps an encrypted key-value store for secrets and credentials. Keep API keys, tokens, and other sensitive values in the database instead of config files or source control, and retrieve them through a simple facade.
Vault is a Laravel 5-era roles & permissions package: a VaultUser trait on your User model, Role/Permission Eloquent models, pivot tables, Blade directives, and route middleware. The repo is archived, so treat it as "maintenance mode" — use it because your Laravel 5.x app already depends on it, not for greenfield work (for new projects reach for spatie/laravel-permission).
Minimal setup in an existing L5 app:
composer require rappasoft/vault
Register the provider in config/app.php:
Rappasoft\Vault\VaultServiceProvider::class,
Publish config and migrations, then migrate:
php artisan vendor:publish --provider="Rappasoft\Vault\VaultServiceProvider"
php artisan migrate
Add the trait to your user model:
use Rappasoft\Vault\Traits\VaultUser;
class User extends Authenticatable
{
use VaultUser;
}
Register the middleware aliases in app/Http/Kernel.php so routes stay readable:
protected $routeMiddleware = [
// ...
'role' => \Rappasoft\Vault\Middleware\CheckRole::class,
'permission' => \Rappasoft\Vault\Middleware\CheckPermission::class,
];
Where to look first: config/vault.php (model bindings, table names), the published migrations, and vendor/rappasoft/vault/src/Traits/VaultUser.php — that trait is the real API surface and the file you'll be reading whenever a check misbehaves.
First use case — protect one admin route and one button:
Route::get('/admin', 'AdminController@index')->middleware('role:admin');
// anywhere you have a $user
if ($user->hasPermission('users.delete')) { /* ... */ }
@role('admin')
<a href="/admin">Admin</a>
@endrole
Seed roles and permissions idempotently. Never hardcode IDs; roles and permissions are data that differ per environment.
// database/seeds/RolesAndPermissionsSeeder.php
$admin = Role::firstOrCreate(['name' => 'admin']);
$editor = Role::firstOrCreate(['name' => 'editor']);
foreach (['posts.create', 'posts.edit', 'posts.delete', 'users.delete'] as $name) {
Permission::firstOrCreate(['name' => $name]);
}
$editor->attachPermission(Permission::where('name', 'posts.edit')->first());
Then $this->call(RolesAndPermissionsSeeder::class); from DatabaseSeeder, and run it in every deploy pipeline (including staging and CI, where migrate:fresh --seed is normal).
Assign at the boundary, check deep inside. Assign roles in registration/onboarding/seeders, then only check in controllers, policies, and views. A useful split:
// Registration / admin action
$user->assignRole('editor');
// Controller
public function destroy(Post $post)
{
abort_unless(auth()->user()->hasPermission('posts.delete'), 403);
$post->delete();
}
Route middleware for coarse access, permission checks for fine access. Roles gate whole areas (/admin/*), permissions gate individual actions. Middleware accepts multiple arguments in the usual Laravel comma syntax (->middleware('role:admin,manager')) — verify the semantics in CheckRole before relying on "any of" vs "all of".
Prefer permissions in code, roles in UI. Naming roles in hasRole('admin') sprinkled through controllers makes renaming a role a refactor. Where practical, check permissions and let admins compose roles from them. If you must check roles, define constants on a small Roles class so typos fail loudly.
Wrap it in a policy layer so the rest of the app talks to Laravel's authorization, not to Vault:
class PostPolicy
{
public function update(User $user, Post $post)
{
return $user->hasPermission('posts.edit')
|| $user->id === $post->user_id;
}
}
Now $this->authorize('update', $post) and @can('update', $post) work, and swapping the underlying package later only touches policies.
Eager-load for menu rendering. If your nav renders role/permission-gated links on every page, load relations once:
$user = User::with('roles.permissions')->find(auth()->id());
Test the matrix, not the widget. Seed roles/permissions in setUp, then assert with $this->actingAs($editor)->get('/posts/create')->assertStatus(200) and the 403 counterpart. Because authorization is data-driven, tests that don't seed permissions silently "pass" as denied — always assert the positive case too.
composer.json, and if you're planning an upgrade, budget a migration to spatie/laravel-permission as part of it rather than fighting the constraints. The data model (roles, permissions, pivots) maps across reasonably well, so plan a data migration, not a rewrite.can() collision. Vault's trait methods and Laravel's Gate can look like competing sources of truth. Pick one vocabulary (hasPermission() for Vault, @can/authorize() for policies) and don't mix them in the same file, or you'll get "why is this 403" bugs that are actually a check against the wrong system.Kernel.php, ->middleware('role:admin') throws a BindingResolutionException. Also re-check Kernel.php after framework-level upgrades that overwrite it.assignRole()/attachPermission(), an already-loaded roles relation on that instance can be out of date. Call $user->load('roles', 'permissions') or $user->refresh() before asserting in tests and before rendering the nav in the same request.php artisan config:cache snapshots the model bindings in config/vault.php. After changing custom role/permission models, clear it (config:clear) — otherwise you get confusing "class not found" or silently default models.config/vault.php at your own Role/Permission subclasses to add behavior (e.g. a display_name, or extra pivot data). Don't edit vendor/ — the trait and models are the seams you're meant to override.onDelete('cascade') if you're seeing ghost permissions survive a role delete.hasPermission('posts.edit') vs posts.edits returns false with no warning, so a gated button just disappears. Keep permission names in constants or config and reference them from both seeders and checks.$user->roles->pluck('name') and $user->permissions->pluck('name') at the point of failure. Nine times out of ten it's "the seeder never ran in this environment" or "the role exists but the permission was never attached to it" — permissions on roles and permissions on users are separate paths, so check both.How can I help you explore Laravel packages today?