secureprompt/laravel-device-approval

Laravel package for trusted-device login approval and device-approval security workflows.

Maintainers

Package info

github.com/DiveshR/laravel-device-approval

pkg:composer/secureprompt/laravel-device-approval

Transparency log

Statistics

Installs: 1

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v0.1.0-beta.1 2026-08-13 18:41 UTC

This package is auto-updated.

Last update: 2026-08-16 07:59:40 UTC


README

Composer: secureprompt/laravel-device-approval
Current release: v0.1.0-beta.1

Trusted-device login approval for Laravel applications: primary credentials remain the responsibility of the host application, while SecurePrompt adds a cryptographic trusted-device approval layer with number matching and a short-lived, one-time approval grant.

Status: v0.1.0-beta.1

SecurePrompt is currently in beta.

It is not yet a production-complete v1.0 release.

Included in this beta

  • Trusted-device enrollment and revocation
  • Cryptographic device credentials
  • Approval challenges
  • Number matching
  • Requester proof
  • One-time approval grants with concurrency-safe consumption
  • Eloquent persistence
  • Laravel HTTP protocol transport
  • Opt-in Laravel web/session authentication adapter
  • Composer package auto-discovery
  • Consumer-install verification using a separate Laravel application
  • SQLite package testing
  • MySQL consumer-flow verification

Explicit beta limitations

  • No Web Push / FCM / APNs / real notification delivery yet
  • No PWA / service worker yet
  • No Sanctum / API-token authentication adapter yet
  • No production first-device enrollment bootstrap policy
  • No SECUREPROMPT_KEY rotation/versioning
  • No complete cross-database CI matrix yet
  • No real phone push-notification test yet

Requirements

  • PHP 8.2+
  • Laravel 12 or 13
  • Dedicated SECUREPROMPT_KEY (not APP_KEY)
  • HTTPS in production (requester proofs, device credentials, and grant secrets travel in HTTP headers)

Installation

composer require secureprompt/laravel-device-approval

Laravel discovers SecurePromptServiceProvider automatically. Do not register it manually.

The package source is publicly available on GitHub. Packagist distribution is being prepared for the beta release. A Composer path repository may still be used for local package development.

1. Generate key

php artisan secureprompt:key

Add to .env:

SECUREPROMPT_KEY=base64:...

Do not commit this value. Changing it later invalidates device credentials and verifier digests. Key rotation is not implemented yet.

2. Migrate

php artisan migrate

Package migrations load automatically (no manual copy required).

3. Optional config publish

php artisan vendor:publish --tag=secureprompt-config

Publishing is optional. Defaults work for local development once SECUREPROMPT_KEY is set.

ApprovalSubject setup

Any authenticatable model may participate. SecurePrompt does not assume App\Models\User.

use SecurePrompt\Laravel\Concerns\UsesSecurePrompt;
use SecurePrompt\Laravel\Contracts\ApprovalSubject;

class User extends Authenticatable implements ApprovalSubject
{
    use UsesSecurePrompt;
}

Critical: do not Auth::attempt() before SecurePrompt

Auth::attempt() creates an authenticated Laravel session immediately. Using it before SecurePrompt approval defeats SecurePrompt as a second factor:

// ❌ WRONG — user is already logged in before device approval
Auth::attempt($credentials);

Correct host flow:

host validates primary credentials (UserProvider / existing auth)
        ↓
Authenticatable candidate obtained (NOT logged in)
        ↓
BeginPendingAuthentication($user, $guard)
        ↓
StartApprovalFromPendingAuthentication (or HTTP start)
        ↓
device confirm → number match → grant
        ↓
POST /secureprompt/session/complete
        ↓
Auth::login + session regenerate

Session auth integration sketch

Enable the adapter:

SECUREPROMPT_SESSION_ENABLED=true
use Illuminate\Support\Facades\Auth;
use SecurePrompt\Laravel\Application\Session\BeginPendingAuthentication;
use SecurePrompt\Laravel\Application\Session\StartApprovalFromPendingAuthentication;

$provider = Auth::guard('web')->getProvider();
$user = $provider->retrieveByCredentials($credentials);

if ($user === null || ! $provider->validateCredentials($user, $credentials)) {
    abort(401);
}

app(BeginPendingAuthentication::class)->begin($user, 'web');
$started = app(StartApprovalFromPendingAuthentication::class)->start();
// Keep requester proof in the Laravel session — never in the query string.

After the trusted device approves and the requester obtains a grant:

POST /secureprompt/session/complete
X-SecurePrompt-Grant-Secret: ...
{ "grant_id": "..." }

See the sibling secureprompt-demo application for a Sail + MySQL consumer example.

Trusted devices

SecurePrompt does not auto-trust the first device. Host applications must implement a deliberate enrollment policy (re-authentication, passkey, existing device approval, etc.).

use SecurePrompt\Laravel\Application\TrustedDevice\EnrollTrustedDevice;
use SecurePrompt\Laravel\Domain\ValueObjects\DeviceIdentifier;

$enrolled = app(EnrollTrustedDevice::class)->enroll(
    $user->securePromptSubjectReference(),
    DeviceIdentifier::from('phone-1'),
    now()->toDateTimeImmutable(),
);

// Deliver $enrolled->credential() once. Never store plaintext in the database.

Configuration

Env / config Purpose
SECUREPROMPT_KEYsecureprompt.crypto.key Dedicated HMAC key
SECUREPROMPT_ENABLEDsecureprompt.enabled When false, HTTP routes are not registered. Orchestration services remain available.
secureprompt.http.prefix Route prefix (default secureprompt)
secureprompt.http.middleware Middleware for device + generic consume routes (default [])
SECUREPROMPT_SESSION_ENABLEDsecureprompt.session.enabled Opt-in Laravel session auth adapter (default false)
secureprompt.session.middleware Middleware for requester + session-complete routes (default ['web'])

HTTP protocol

Routes load when secureprompt.enabled=true.

Method Path Auth material
POST /secureprompt/approvals Subject resolver (pending session when session auth enabled)
POST /secureprompt/approvals/{challenge}/confirm device_id + X-SecurePrompt-Device-Credential
POST /secureprompt/approvals/{challenge}/reject device id + credential
POST /secureprompt/approvals/{challenge}/number-match device + { "number": 47 }
POST /secureprompt/approvals/{challenge}/status X-SecurePrompt-Requester-Proof
POST /secureprompt/approvals/{challenge}/grant requester proof
POST /secureprompt/grants/{grant}/consume X-SecurePrompt-Grant-Secret (authorization only — no Auth::login())
POST /secureprompt/session/complete grant id + secret (session auth only — consume + login)

Status is POST because it requires a secret proof and may persist expiry. Never put secrets in query strings.

Security architecture (summary)

  • Possession secrets travel only in dedicated headers
  • Clients cannot assert subject_id / subject_type to impersonate another subject
  • Session pending context is server-side; grant theft without matching pending session is denied by the session adapter
  • RequesterProof remains mandatory even with Laravel session binding
  • This package never sets Access-Control-Allow-Origin: *
  • Host rate limiting is recommended (not built-in yet)

Development

Docker-first package workflow:

docker compose run --rm php composer install
docker compose run --rm php composer check

Individual scripts: composer test, composer lint, composer analyse, composer validate --strict.

License

MIT