serenity_technologies/admin-dashboard-guard

Two-factor passphrase+password admin authentication guard for any admin-restricted Laravel page (Horizon, Telescope, Pulse, custom routes, etc.).

Maintainers

Package info

github.com/Serenity-Technologies/admin-dashboard-guard

pkg:composer/serenity_technologies/admin-dashboard-guard

Statistics

Installs: 5

Dependents: 0

Suggesters: 0

Stars: 1

Open Issues: 0

v1.0.1 2026-05-24 03:59 UTC

This package is auto-updated.

Last update: 2026-05-24 06:38:03 UTC


README

A Laravel package that places a two-factor passphrase + password authentication wall in front of any admin-restricted page — Horizon, Telescope, Pulse, Filament, or any custom route — with zero extra database tables.

How It Works

Access to a protected page requires two sequential steps:

  1. Passphrase step — The admin provides a short-lived secret passphrase (bcrypt-hashed in the database). On success, a 10-minute session window opens.
  2. Password step — Within that window, the admin enters their account password to receive a fully-authenticated session (valid for 60 minutes by default).

Each protected page runs its own independent session, so authenticating to /horizon does not grant access to /telescope.

Generating a passphrase via Artisan emails it directly to the admin and schedules automatic revocation after a configurable delay.

Requirements

Requirement Version
PHP ^8.1
Laravel ^11.0 | ^12.0 | ^13.0
Laravel Sanctum (optional) ^3.0 | ^4.0

Sanctum is only needed if you want to accept a bearer-token as an alternative to the session flow. It is listed under suggest in composer.json and is not required by default.

Installation

1. Require the package

composer require serenity_technologies/admin-dashboard-guard

The service provider is auto-discovered via extra.laravel.providers in composer.json.

2. Publish the config

php artisan vendor:publish --tag=admin-dashboard-guard-config

This creates config/admin-dashboard-guard.php.

3. Add the passphrase column to your admin model

php artisan make:migration add_passphrase_to_admins_table
// In the migration:
$table->string('passphrase')->nullable();

Run the migration:

php artisan migrate

4. Configure your admin model

Set the model in .env:

ADMIN_DASHBOARD_MODEL=App\Models\Admin

Your model must implement Illuminate\Contracts\Auth\Authenticatable and have both a passphrase column (nullable, stores a bcrypt hash) and a password column.

5. Configure the auth guard

Ensure an admin guard is defined in config/auth.php:

'guards' => [
    'admin' => [
        'driver'   => 'session',
        'provider' => 'admins',
    ],
],

'providers' => [
    'admins' => [
        'driver' => 'eloquent',
        'model'  => App\Models\Admin::class,
    ],
],

Then set in .env:

ADMIN_DASHBOARD_GUARD=admin

Protecting a Page

The package registers a named middleware alias for every entry in protected_pages. The alias format is admin.{key}.

Horizon

In config/horizon.php:

'middleware' => ['web', 'admin.horizon'],

Telescope

In app/Providers/TelescopeServiceProvider.php:

protected function gate(): void
{
    Gate::define('viewTelescope', fn () => auth('admin')->check());
}

And add the middleware to the route group in your telescope service provider or routes/web.php:

Route::middleware(['web', 'admin.telescope'])->group(function () {
    // Telescope routes
});

Any Custom Route

You can protect any route or route group with the admin.{key} alias, where key matches an entry in protected_pages:

// Protect Pulse
Route::middleware(['web', 'admin.pulse'])->group(function () {
    Route::get('/pulse', ...);
});

// Protect a custom admin panel
Route::middleware(['web', 'admin.myadmin'])->prefix('admin')->group(function () {
    // your admin routes
});

Adding a New Protected Page

Open config/admin-dashboard-guard.php and add an entry to protected_pages:

'protected_pages' => [

    'horizon' => [
        'path'           => 'horizon',
        'gate'           => 'viewHorizon',
        'session_prefix' => 'horizon',
        'passphrase_ttl' => 10,
        'session_ttl'    => 60,
        'theme_color'    => 'purple',
    ],

    'telescope' => [
        'path'           => 'telescope',
        'gate'           => 'viewTelescope',
        'session_prefix' => 'telescope',
        'passphrase_ttl' => 10,
        'session_ttl'    => 60,
        'theme_color'    => 'indigo',
    ],

    // Add any new page here:
    'pulse' => [
        'path'           => 'pulse',
        'gate'           => 'viewPulse',   // set null to skip gate registration
        'session_prefix' => 'pulse',
        'passphrase_ttl' => 10,
        'session_ttl'    => 60,
        'theme_color'    => 'rose',
    ],

],

The package automatically:

  • Registers an admin.pulse middleware alias
  • Defines a viewPulse gate (or skips it if gate is null)
  • Registers GET /pulse/password and POST /pulse/password routes for the password step

protected_pages Options

Key Type Default Description
path string (key name) URL path segment (e.g. 'horizon'/horizon)
gate string|null 'view{Key}' Gate name to define. Set null to skip.
session_prefix string (key name) Namespace prefix for session keys
passphrase_ttl int 10 Minutes the passphrase verification window is valid
session_ttl int 60 Minutes the fully-authenticated session is valid
theme_color string 'blue' Tailwind colour name used in the login views
stealth_mode bool false (global) When true, unauthenticated requests receive 404. Overrides the global default for this page.

Stealth Mode

When stealth mode is enabled the auth wall is completely invisible to unauthenticated requests. The passphrase form is never shown — every bare access attempt returns a plain 404 Not Found, exactly as if the route did not exist.

The only way in is to carry the passphrase directly in the URL query string:

https://yourdomain.com/horizon?passphrase=a8Kq2mRvXpLtN4sYdZbWcJeF7hUgOiTy

A valid passphrase silently redirects to the password step as normal. A wrong passphrase also returns 404 (not 401), so nothing about the auth wall is revealed.

Enable globally (all protected pages)

# .env
ADMIN_DASHBOARD_STEALTH=true

Or directly in the published config:

'stealth_mode' => true,

Enable per page (overrides the global default)

'protected_pages' => [
    'horizon' => [
        // ...
        'stealth_mode' => true,   // Horizon: 404 when unauthenticated
    ],
    'telescope' => [
        // ...
        'stealth_mode' => false,  // Telescope: still shows the passphrase form
    ],
],

Tip: Combine stealth mode with a short --delay when generating passphrases so the URL is valid only for a small window of time.

Artisan Commands

All commands are prefixed with admin-guard: to avoid collisions with the host application.

Generate a passphrase for one admin

php artisan admin-guard:generate-passphrase admin@example.com

Generates a random passphrase, bcrypt-hashes and saves it to the admin record, emails it to the admin, then queues an auto-removal job after --delay hours.

Option Default Description
--bcc= BCC address for the notification email
--length= 32 Passphrase length (minimum 16)
--delay= 1 Hours before the passphrase is automatically revoked

Example output:

Passphrase generated for: John Smith (admin@example.com)
Passphrase : a8Kq2mRvXpLtN4sYdZbWcJeF7hUgOiTy
Auto-removes in 1 hour(s).

Generate passphrases for all admins

php artisan admin-guard:generate-passphrases

Runs the single-admin flow for every record returned by the configured model. Accepts the same --bcc, --length, and --delay options.

Remove a passphrase for one admin

php artisan admin-guard:remove-passphrase admin@example.com

Immediately nulls out the passphrase column for the given admin, invalidating any active passphrase-step session.

Clear passphrases for all admins

php artisan admin-guard:clear-passphrases

Nulls the passphrase column for every admin that currently has one set. Prompts for confirmation unless --force is passed.

Option Description
--force Skip the confirmation prompt (useful in scripts or CI)

Incident response tip: Run admin-guard:clear-passphrases --force to instantly revoke all active passphrases if you suspect a passphrase has been compromised.

Configuration Reference

// config/admin-dashboard-guard.php

return [

    // The Laravel auth guard used to authenticate the admin.
    'guard' => env('ADMIN_DASHBOARD_GUARD', 'admin'),

    // Fully-qualified Eloquent model class.
    'model' => env('ADMIN_DASHBOARD_MODEL', null),

    // Column names on the model.
    'passphrase_column' => env('ADMIN_DASHBOARD_PASSPHRASE_COLUMN', 'passphrase'),
    'password_column'   => env('ADMIN_DASHBOARD_PASSWORD_COLUMN', 'password'),

    // Accept a valid Sanctum personal-access token as an alternative to the
    // session flow. Requires laravel/sanctum.
    'sanctum_support' => env('ADMIN_DASHBOARD_SANCTUM', true),

    // Pages to protect — see "Adding a New Protected Page" above.
    'protected_pages' => [ /* ... */ ],

];

Environment Variables

Variable Default Description
ADMIN_DASHBOARD_GUARD admin Auth guard name
ADMIN_DASHBOARD_MODEL null Fully-qualified model class
ADMIN_DASHBOARD_PASSPHRASE_COLUMN passphrase Column storing the bcrypt passphrase hash
ADMIN_DASHBOARD_PASSWORD_COLUMN password Column storing the bcrypt password hash
ADMIN_DASHBOARD_SANCTUM true Enable Sanctum bearer-token fallback

Customising the Views

Publish the Blade views to override them:

php artisan vendor:publish --tag=admin-dashboard-guard-views

Files land in resources/views/vendor/admin-dashboard-guard/:

View Purpose
passphrase-login.blade.php Step 1 — passphrase entry form
password-login.blade.php Step 2 — password entry form
emails/admin-passphrase.blade.php Passphrase notification email

Each view receives $toolName, $toolPath, and $themeColor variables.

Customising the Passphrase Email Subject

Pass a custom subject when constructing the mailable directly:

use SerenityTechnologies\AdminDashboardGuard\Mail\AdminPassphrase;

Mail::to($admin)->send(new AdminPassphrase($passphrase, 'Your Dashboard Passphrase'));

Sanctum Bearer Token Fallback

When sanctum_support is true and laravel/sanctum is installed, the middleware also accepts a valid personal-access token belonging to an instance of the configured model class. This lets programmatic/API clients access protected pages without going through the browser-based session flow.

Security Notes

  • Passphrases are never stored in plain text — only a bcrypt hash is saved in the database.
  • Each passphrase hash is verified with Hash::check() by iterating admin records; no plain-text comparison occurs.
  • The passphrase step has a configurable short TTL (10 minutes by default) and is cleared from the session once the password step completes.
  • The fully-authenticated session is TTL-scoped per protected page, so sessions for /horizon and /telescope are completely independent.
  • Use admin-guard:clear-passphrases --force to invalidate all active passphrases immediately during a security incident.
  • The auto-removal delay (--delay) is enforced via a queued job so that passphrases can never be left active indefinitely by accident.

License

MIT