sashalenz / passcode-lock
Laravel passcode lock with morph relationship, idle timeout and keyboard shortcut
Requires
- php: ^8.5
- illuminate/contracts: ^12.0|^13.0
- illuminate/database: ^12.0|^13.0
- illuminate/http: ^12.0|^13.0
- livewire/livewire: ^4.2
- spatie/laravel-package-tools: ^1.92
- xitedev/wireforms: ^3.3.3
Requires (Dev)
- orchestra/testbench: ^10.0|^11.0
- pestphp/pest: ^4.4
- pestphp/pest-plugin-laravel: ^4.1
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
Laravel package for passcode (4-digit PIN) lock screen with morph relationship to any model, auto-lock on inactivity, and manual lock via keyboard shortcut.
Requirements
- PHP ^8.5
- Laravel ^12.0|^13.0
- Livewire ^4.2
- xitedev/wireforms ^3.3
- Alpine.js (loaded via Vite)
- Tailwind CSS v4
Designed for projects with Livewire + Wireforms + Alpine.js + Tailwind.
Installation
composer require sashalenz/passcode-lock
Publish config (optional, to override defaults):
php artisan vendor:publish --tag=passcode-lock-config
Implementation
1. Migrations
The package registers create_passcodes_table migration. The passcodes table is created when you run:
php artisan migrate
Table structure:
idpasscodeable_type,passcodeable_id(morph)code(hash)timeout_minutes(default 10)timestamps
2. Model with passcode
Add the HasPasscodeLock trait to the model that should have a passcode (e.g. User or Admin):
use Sashalenz\PasscodeLock\Traits\HasPasscodeLock; class Admin extends Authenticatable { use HasPasscodeLock; // ... }
Trait methods:
| Method | Description |
|---|---|
passcode() |
MorphOne to Passcode |
hasPasscodeEnabled(): bool |
Whether passcode is set |
getPasscodeTimeoutMinutes(): int |
Inactivity timeout (minutes) |
verifyPasscode(string $plainCode): bool |
Verify entered code |
setPasscode(?string $plainCode, int $timeoutMinutes = 10): void |
Set or clear passcode |
updatePasscodeTimeout(int $timeoutMinutes): void |
Change timeout only |
Passcode is 4 digits; hashed via Laravel Hash.
3. Configuration
File config/passcode-lock.php (or published):
- guard — single guard name (e.g.
admin,web). Used ifguardsis not set. - guards — array of guards for multi-guard passcode lock (e.g.
['admin', 'web']). Overridesguardwhen set. Session state (last_activity, locked_at) is stored separately per guard; on lock,locked_guardis saved for unlock. - timeout_minutes — default inactivity timeout (minutes).
- session_keys — session keys for last_activity, locked_at, unlocked_at, locked_guard. With multiple guards, keys (except locked_guard) get a suffix.
- allowed_route_names_when_locked — route names allowed when locked.
- view — lock screen view (default
passcode-lock::lock). - redirect_after_unlock — route after unlock (string or array per guard:
['admin' => 'admin.index', 'web' => 'home']). - login_route, logout_route — login/logout routes (string or array per guard).
Environment variables (optional):
PASSCODE_LOCK_GUARD— single guardPASSCODE_LOCK_GUARDS— multiple guards, comma-separated, e.g.admin,webPASSCODE_TIMEOUT_MINUTESPASSCODE_LOCK_IDLE_CHECK_MS— idle check interval in ms (default 30000)
Multiple guards
To use passcode lock for both admin and web user (or other guards):
- Set
guardsin config (orPASSCODE_LOCK_GUARDS=admin,web). - Models for each guard (e.g.
Admin,User) must use theHasPasscodeLocktrait. - Set
redirect_after_unlockand, if needed,login_routeas arrays per guard. - Middleware detects the current guard; on lock it stores
locked_guard, on unlock it uses that guard.
4. Middleware
Add middleware to the web group (e.g. in bootstrap/app.php):
use Sashalenz\PasscodeLock\Http\Middleware\PasscodeLockMiddleware; $middleware->appendToGroup('web', [ // ... PasscodeLockMiddleware::class, ]);
Or via alias:
$middleware->appendToGroup('web', ['passcode-lock']);
Middleware:
- Runs only for users of configured guard(s).
- Requires
hasPasscodeEnabled()method (i.e. model uses the trait). - When passcode is enabled: updates last_activity, sets locked_at and redirects on inactivity ≥ timeout.
- Allowed routes when locked — from
allowed_route_names_when_locked.
5. Routes
Register package routes (e.g. in routes/web.php):
use Sashalenz\PasscodeLock\Http\Controllers\LockController; // Do not protect with auth so unlock works without re-login $router->get('lock', [LockController::class, 'show'])->name('lock.show'); $router->post('lock', [LockController::class, 'unlock'])->name('lock.unlock'); $router->get('lock/trigger', [LockController::class, 'trigger']) ->middleware('auth:admin') // or your guard ->name('lock.trigger');
- lock.show — PIN entry screen.
- lock.unlock — POST with
codefield (4 digits). - lock.trigger — instant lock (for idle script and hotkey); must be auth-protected.
6. Lock screen view (Tailwind + Vite)
The package provides view passcode-lock::lock with Tailwind CSS and Vite layout. No color hardcoding — uses Tailwind utilities (primary-*, gray-*), overridable via @theme.
Tailwind content — add package views in resources/css/app.css:
@source "../../packages/sashalenz/passcode-lock/resources/views/**/*.blade.php"; @source "../../vendor/sashalenz/passcode-lock/resources/views/**/*.blade.php";
Config options:
| Key | Description |
|---|---|
view |
View name (default passcode-lock::lock). |
layout |
Layout for lock view (default passcode-lock::layouts.lock). Must load Vite and Tailwind. |
vite_entry |
Vite entry points (default ['resources/css/app.css', 'resources/js/app.js']). |
page_title, app_name |
Page title and app name. |
logo_url, logo_alt |
Logo. |
custom_css_url |
URL for additional CSS. |
Theme — prefers-color-scheme and localStorage.theme.
7. Profile passcode settings (Livewire + Wireforms)
The package provides a Livewire component with Wireforms fields (checkbox, text, button). Add it in your profile card:
<x-wiretables::card> <x-slot name="header">{{ __('passcode-lock::passcode.section_title') }}</x-slot> <div class="p-2"> <livewire:passcode-lock.passcode-settings guard="admin" /> </div> </x-wiretables::card>
Layout must include <x-wireforms::notification/> for success/error messages.
8. Idle and hotkey (Alpine.js)
In the layout where auto-lock and Ctrl+Alt+L manual lock are needed:
@include('passcode-lock::idle-script')
The partial registers Alpine component passcodeIdle and renders it only for users with passcode enabled. Config idle_check_interval_ms (env: PASSCODE_LOCK_IDLE_CHECK_MS) — check interval in ms (default 30000).
9. Custom passcode form (without package component)
If not using the package component, call trait methods in your own form:
- Enabled:
hasPasscodeEnabled(), disable —setPasscode(null). - Timeout:
setPasscode($code, $timeoutMinutes)orupdatePasscodeTimeout($timeoutMinutes). - New PIN: validate
digits:4,confirmed; save —setPasscode($plainCode, $timeoutMinutes).
Translations
The package provides translations in resources/lang/{en,uk}/auth.php and passcode.php (namespace passcode-lock::auth, passcode-lock::passcode). Keys: locked, application_locked, enter_passcode, code, logout, unlock, invalid_passcode, and passcode form labels.
Publish (optional):
php artisan vendor:publish --tag=passcode-lock-translations
Migrating from columns to passcodes table
If passcode was stored in model columns (passcode_enabled, passcode_code, passcode_timeout_minutes), migrate data to the passcodes table and drop those columns. Example migration — in the app repository using this package (e.g. migrate_passcodes_to_package_and_drop_columns).
License
MIT.