Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Larastarter Laravel Package

smarttechtank/larastarter

Larastarter by SmartTechTank is a Laravel starter kit with a ready-made admin foundation: authentication, roles and permissions, user management, CRUD scaffolding, and a clean UI to help you launch new PHP apps faster.

View on GitHub
Deep Wiki
Context7

Getting Started

This release adds a first-class disable 2FA flow. The fastest way to understand the package's conventions is to read the two classes it just shipped: TwoFactorAuthController (the disable method) and TwoFactorDisableRequest (the validation contract). The published TwoFactorAuthDisableTest doubles as executable documentation for the expected request shape.

Minimal setup:

composer require <vendor>/<package>

php artisan vendor:publish --provider="<Vendor>\<Package>\<PackageServiceProvider>"
php artisan migrate

Then wire the package into your authenticatable model:

use <Vendor>\<Package>\Concerns\HasTwoFactorAuth;

class User extends Authenticatable
{
    use HasTwoFactorAuth;
}

First use case to get working end-to-end: enable → confirm → log in with a code → disable with reauthentication. If that loop works in a feature test, the rest of the package is mostly configuration.

Where to look first, in order:

  1. config/<package>.php — issuer name, code window/drift, recovery code count, route middleware.
  2. routes/ — the enable/confirm/disable/challenge endpoints and their middleware stack.
  3. TwoFactorDisableRequest — the exact fields the disable endpoint accepts.
  4. The published test suite — the assertions tell you what state the package expects on the user record.

Implementation Patterns

Enabling is a two-phase flow. Most packages (and this one, judging by the controller shape) separate generating a secret from activating it. Don't mark 2FA as enabled on the user until the user proves possession of the device:

// Phase 1: issue secret + QR payload, keep it in session/pending state
// Phase 2: verify the submitted code, then persist and flip the enabled flag

Gate the login challenge outside your login controller. Rather than branching inside LoginController, hook the challenge as a middleware or a Login event listener that redirects users with 2FA enabled to the challenge route and marks the session as "2FA passed" only after a valid code. This keeps Fortify/Breeze/Jetstream integration from fighting your custom logic.

Disabling now requires reauthentication — lean on that. The new controller method accepts a password or a verification code. Route it behind your password-confirmation middleware and treat it as a sensitive action:

Route::delete('/user/two-factor-authentication', [TwoFactorAuthController::class, 'disable'])
    ->middleware(['auth', 'password.confirm', 'throttle:6,1']);

Because TwoFactorDisableRequest is a dedicated FormRequest, you have a clean seam for customization: override authorize() to restrict self-disable (e.g. block users in an enforced-2FA group), and override rules() to require a password instead of accepting a code, or vice versa.

Post-disable cleanup is your responsibility. In the same transaction as clearing the secret, regenerate recovery codes, forget any "2FA verified" session flag, and fire an audit event:

DB::transaction(function () use ($user) {
    $user->forceFill(['two_factor_secret' => null, 'two_factor_confirmed_at' => null])->save();
    $user->recoveryCodes()->delete();
    session()->forget('2fa.passed');
    event(new TwoFactorDisabled($user));
});

Enforce 2FA selectively with middleware, not with a global check in the controller. A small RequireTwoFactor middleware that bounces unenrolled users to the setup screen is far easier to reason about than scattering conditionals, and it composes with role/permission checks.

Test with the shipped test as a template. Mirror TwoFactorAuthDisableTest: actingAs($user), satisfy the password-confirmation window (or post('/password/confirm') first), assert the secret is nulled and recovery codes are gone. Use a fake TOTP generator or the package's code helper rather than asserting on hardcoded codes.

Integration tips: if the app already uses Fortify's own 2FA columns, pick one source of truth before enabling this package — dual traits on the same model is the most common source of "2FA randomly stops working" bugs.

Gotchas and Tips

  • Reauthentication windows bite in tests and in long sessions. password.confirm stores a timestamp governed by auth.password_timeout; a test that confirms a password and then travels through several requests can fail once the window lapses. Set the timeout explicitly in the test's config.
  • Never expose disable as a GET. Use DELETE/POST with CSRF, and throttle it. A disable endpoint that accepts a code is a second authentication surface and deserves the same rate limiting as login.
  • The request class ships via the install command. If you previously published the controller or tests, re-run the publish command with --force selectively — don't blanket-force config or migrations you've already edited. Verify TwoFactorDisableRequest actually exists in your app namespace after upgrading, otherwise the injected controller method will 500.
  • Encrypt the secret at rest. Use an encrypted cast on the secret column; a plaintext TOTP seed is equivalent to a password hash you can reverse.
  • Clock drift. Allow a window of 1 (occasionally 2) time steps. A window of 0 causes intermittent failures on devices with slow clocks — the classic "works for me, not for the user" bug.
  • Recovery codes are one-time. Consume and persist removal atomically, or a race lets the same code be used twice. Hash them the way the package expects; don't invent your own column.
  • Compare codes in constant time (hash_equals) if you add custom verification paths — a naive === on a 6-digit code is a timing oracle.
  • Disabling 2FA should notify the user. Send a notification on disable (and on recovery-code use). This is the only signal an account owner gets that someone with a hijacked session turned off their protection.
  • Debugging the challenge loop: if users bounce between challenge and dashboard, check whether the "passed" flag is stored in the session (lost on regeneration) versus a DB column. session()->regenerate() after login is the usual culprit.
  • Extension points worth knowing: the code generator/formatter binding (swap for an SMS or email driver), the disable FormRequest (policy overrides), and model events on successful enable/disable (for audit logs). Prefer these over editing published vendor files, which you'll have to re-merge on every release.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
robertmarney/lara-hierarchial-collections
simonschaufi/rule-doc-generator
emissaoja/emissaoja
blackbricksoftware/gravityforms-civicrm-form-processor
al-rahabi/fancy-crud
jinas/dhivatar-component
mltstephane/laravel-quality
bernskiold/laravel-talentlms
bernskioldmedia/laravel-form-architect
youbar/easy-crud
c-point-labs/laravel-hostyourai
dbravoan/dbcms-admin-theme-dbcms
dbravoan/dbcms-theme-db-cyber
dbravoan/dbcms-blocks-basic
dbravoan/dbcms-module-content-audit
dbravoan/dbcms-module-ai-assistant
paygate-to/paygate-php
padosoft/askmydocs-connector-base
defstudio/supercharged-carbon
bernskioldmedia/laravel-matomo