Weave Code
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

Laravel Auth Laravel Package

joe-404/laravel-auth

Config-driven, drop-in auth for Laravel 12/13: JSON API for registration with OTP/magic-link verification, login, refresh tokens, password reset, Google OAuth, multi-session/device fingerprinting, long-lived API tokens, account status workflows, and referrals.

View on GitHub
Deep Wiki
Context7

Upgrading Guide

Version history for joe-404/laravel-auth. Every release is documented — what was added, what was fixed, and whether the version should be used in production.

Outdated versions are marked with a warning. Do not pin to them. They contain known bugs and missing security patches. Always use the latest patch release in the current minor series.


v2.6.1 → v2.7.3 — see root docs for full notes

This file's per-version sections currently stop at v2.6.0. For full per-version notes on v2.6.1, v2.7.0, v2.7.1, v2.7.2, and v2.7.3, see the canonical UPGRADING.md and CHANGELOG.md at the repo root.

Quick summary of what's new since v2.6.0:

  • v2.6.1 — security hardening: social/reset 2FA gates, auth.step-up middleware, HMAC OTP, fragment tokens, auth.active on admin groups, password floor 15.
  • v2.7.0 — ⚠ mis-tagged on the v2.6.1 commit by mistake; composer require :2.7.0 actually installs v2.6.1 code. Do not pin to it — use v2.7.3.
  • v2.7.1 — the real v2.7 security pass: email-2FA strict-DB fix, timing-safe login, refresh hash strip, UUID-safe step-up cache keys, TOTP replay protection, switch + password-confirm rate limits, APP_KEY boot guard, denylist hardening, required 2FA enforcement on package routes, opt-in strict API-token abilities, admin role hierarchy, OAuth state for stateless clients, configurable trusted-device level, security.profile preset (relaxed/balanced/high).
  • v2.7.2 — concurrency hardening: atomic single-use for backup codes, phone OTP, and TOTP replay; admin API-token PATCH/DELETE step-up symmetry.
  • v2.7.3 — atomic 2FA challenge consumption across factors; admin referral routes through AdminGate; deleted-account snapshot strips sensitive fields; optional user-side revoke step-up.

The per-version sections below will be migrated into this file in a dedicated docs sprint.


Table of Contents


Upgrading steps

Upgrading to v2.6.0 from v2.5.x

v2.6 is additive — existing users without 2FA enrolled see no flow changes, no API breakage. The upgrade is one command plus a small User-model edit.

1. Composer update

composer update joe-404/laravel-auth

Two new dependencies are pulled in automatically:

  • pragmarx/google2fa: ^8.0 — RFC 6238 TOTP implementation
  • bacon/bacon-qr-code: ^3.0 — QR-code SVG generator for authenticator-app enrollment

2. Run the upgrade migrations

php artisan auth:install --upgrade

This runs only the new 2025_v260_* migrations (it skips re-publishing Sanctum/Spatie migrations) and prints a feature summary. New tables:

  • auth_two_factor_methods
  • auth_two_factor_backup_codes
  • auth_two_factor_challenges
  • auth_trusted_devices
  • auth_phone_otp_codes

New columns on users: phone, phone_verified_at, two_factor_required.

3. Update your User model

protected $fillable = [
    // …existing…
    'phone', 'phone_verified_at', 'two_factor_required',
];

protected $casts = [
    // …existing…
    'phone_verified_at'   => 'datetime',
    'two_factor_required' => 'bool',
];

4. (Optional) Re-publish config to see the new sections

php artisan vendor:publish --tag=auth-config --force

Three new sections appear — phone, two_factor, trusted_devices. All v2.6 features are disabled or non-intrusive by default; your app behaves exactly as on v2.5 until you opt in.

5. Enable what you want

Phone capture + verification. Default driver is log (writes codes to the Laravel log — dev only). In production set a real provider:

AUTH_PHONE_ENABLED=true
AUTH_PHONE_REQUIRED=false              # nullable at registration
AUTH_PHONE_VERIFY_AT_REG=false         # verify later, not at register
AUTH_PHONE_SMS_PROVIDER=infobip
INFOBIP_API_KEY=...
INFOBIP_BASE_URL=https://api.infobip.com

Built-in providers: log, infobip, messagecentral, twilio, firebase. Custom providers register via PhoneDriverManager::extend() — see docs/customization.md.

Two-factor authentication.

AUTH_2FA_ENABLED=true                  # on by default
AUTH_2FA_REQUIRED=false                # per-user opt-in, not forced
AUTH_2FA_DEFAULT=totp                  # totp | email | sms
AUTH_2FA_MIDDLEWARE=password_confirm   # block | force_enroll | password_confirm

Enrollment (post-login): POST /auth/2fa/enroll/totp/start → scan QR → POST /auth/2fa/enroll/totp/verify. The first method's verify response returns backup_codes once.

Login becomes a two-step flow once 2FA is enrolled:

POST /auth/login        → { requires_2fa, challenge_token, available_methods }
POST /auth/2fa/challenge → { token, refresh_token, trusted_device_token? }

Trusted devices.

AUTH_TRUSTED_DEVICES_ENABLED=true
AUTH_TRUST_BYPASS_MIN=high             # devices >= high skip the 2FA challenge
AUTH_TRUST_LEVEL_MODE=time             # time | time_consistent | time_admin

Trusted-device 2FA bypass requires two signals — not fingerprint alone. When a device is trusted, the package issues a one-time trusted_device_token (returned in the registration response and in /auth/2fa/challenge when trust_device=true). The client must send it back as the X-Trusted-Device-Token header — together with X-Browser-Fingerprint — for the bypass to apply. Fingerprint is client-controlled and never bypasses on its own. Store the token in mobile Keychain or an HttpOnly cookie; it is returned exactly once.

Social sign-in when you require custom fields. OAuth (Google) gives you the user's email + name but never your app's required fields (username, phone, country…). Enable profile completion so a brand-new social user is asked for those fields before the account is created:

AUTH_SOCIAL_PROFILE_COMPLETION=true      # default false (legacy: create + log in immediately)
AUTH_SOCIAL_PROFILE_COMPLETION_TTL=15    # minutes the completion token is valid

With it on, the callback for a brand-new user returns a completion step instead of a token:

GET /auth/social/google/callback
  → 202 { requires_profile_completion: true, completion_token, prefill: { email, name, avatar } }

POST /auth/social/complete   { completion_token, username, phone, … }
  → validates the SAME registration.extra_fields_rules + phone rules as the email flow
  → creates the user, links the social account, issues the real token

No user row is created until /auth/social/complete succeeds — an abandoned onboarding leaves nothing behind, exactly like the 3-step email flow. Only required fields block; optional ones can be filled later. Existing users (and all users when the flag is off) keep logging in directly.

6. Protect sensitive endpoints with step-up (optional)

Route::middleware(['auth:sanctum', 'auth.2fa'])->group(function () {
    Route::delete('billing/subscription', /* … */);
});

auth.2fa issues a fresh 2FA challenge (or password-confirm fallback for users without 2FA). See docs/middleware.md for the full middleware reference.

Behavior changes to be aware of

Behavior v2.5 v2.6
Login, 0 enrolled 2FA methods issues token unchanged
Login, ≥1 enrolled 2FA method n/a returns challenge_token first
Trusted device w/ fingerprint + token ≥ bypass level n/a skips 2FA, issues token
New Google user, profile_completion off creates + logs in from Google profile unchanged
New Google user, profile_completion on n/a returns requires_profile_completion; account created only at /auth/social/complete
UserLoggedIn event fires at login still fires at credential success even when 2FA pending; new TwoFactorChallengeIssued + TwoFactorVerified events added

Rolling back

php artisan migrate:rollback --step=7
composer require joe-404/laravel-auth:^2.5.1

Upgrading to v2.5.1 from v2.5.0

No breaking changes, no migrations. This is a security and correctness pass on the refresh-token flow plus a handful of hardening fixes.

composer require joe-404/laravel-auth:^2.5
php artisan vendor:publish --tag=auth-config --force   # pick up new optional keys

Behavior changes you should be aware of:

  1. Refresh now re-checks account state. A suspended, disabled, soft-deleted, or unverified user calling POST /auth/refresh will be rejected — they used to get a fresh token pair. Frontends should handle the same error responses they already handle on login.

  2. Refresh now keeps the session row in sync. After rotation, auth_sessions_extended.sanctum_token_id is repointed at the new access token. Sessions listings, DELETE /auth/sessions/{id}, and last-active tracking now keep working past the first refresh.

  3. GeoIP lookup is now queued. If you have auth_system.device.resolve_location=true, the country/city resolution is now dispatched as a BackfillSessionLocation job. The session row is created with country/city = null and the values fill in once the queue worker runs. A queue worker must be running for the columns to populate. The endpoint is now HTTPS (https://ip-api.com/json/{ip} by default).

  4. X-Browser-Fingerprint is now format-validated. Values that are not hex digests within [32, 128] characters are silently ignored (treated as absent). If your frontend was sending something other than a hex digest, switch to one — e.g. SHA-256 of the canvas/WebGL/screen signals.

  5. ApiTokenAuth no longer echoes raw exception messages. Clients calling endpoints behind auth.api-token will see "Invalid API token." for unknown errors instead of the underlying exception message. The original is logged via Log::error.

  6. isAuthRoute() now respects AUTH_ROUTES_PREFIX. If you mounted the package under a custom prefix (e.g. api/v1/auth) on v2.5.0 or earlier, validation and authentication failures on those routes were not being wrapped in the package JSON envelope. They are now.

New optional .env / config keys (safe to leave at defaults):

# When true (default), POST /auth/refresh rejects unverified users.
# Set false to keep legacy behavior (verification only enforced at login).
AUTH_VERIFICATION_REQUIRED_FOR_REFRESH=true
// config/auth_system.php

'verification' => [
    'required_for_refresh' => env('AUTH_VERIFICATION_REQUIRED_FOR_REFRESH', true),
],

'referral_code' => [
    'browser_fingerprint_min_length' => 32,
    'browser_fingerprint_max_length' => 128,
],

'device' => [
    'location_endpoint' => 'https://ip-api.com/json/{ip}',
    'location_queue'    => 'default',
],

The free ip-api.com plan only allows HTTP. If you are using the free plan, either override device.location_endpoint back to http://ip-api.com/json/{ip} (note: cleartext transport) or switch to a provider that supports HTTPS on the free tier.


Upgrading to v2.5.0 from v2.4.x

No breaking changes. Run migrations, publish the updated config to pick up the new referral_code and device keys.

composer require joe-404/laravel-auth:^2.5
php artisan migrate
php artisan vendor:publish --tag=auth-config --force

New .env variables (all optional — safe to ignore if features not needed):

# Referral codes
AUTH_REFERRAL_CODE_ENABLED=false
AUTH_REFERRAL_CODE_LENGTH=10
AUTH_REFERRAL_CODE_UPPERCASE=true
AUTH_REFERRAL_REDEEM_WINDOW=120
AUTH_REFERRAL_ALLOWED_CLIENTS=both
AUTH_REFERRAL_ABUSE_SAME_IP=flag
AUTH_REFERRAL_ABUSE_SAME_DEVICE=block
AUTH_REFERRAL_ABUSE_BOTH=block

New tables created by migrations:

  • referrals — referral relationship, status, fingerprint snapshots, abuse flags
  • auth_user_devices — permanent per-user device history (survives logout)
  • fingerprint_hash column added to auth_sessions_extended

Upgrading to v2.4.x from v2.3.x

No breaking changes for most apps. Run migrations and publish the updated config.

composer require joe-404/laravel-auth:^2.4
php artisan migrate
php artisan vendor:publish --tag=auth-config --force   # pick up new account.* keys

Recommended User model changes:

use Illuminate\Database\Eloquent\SoftDeletes;
use Joe404\LaravelAuth\Concerns\HasAccountStatus;

class User extends Authenticatable
{
    use HasApiTokens, HasRoles, Notifiable, SoftDeletes, HasAccountStatus;
}

SoftDeletes is required if you want account deletion auto-restore to work. HasAccountStatus is optional sugar.

New schema columns added to users:

  • account_status (varchar 32, default active)
  • status_changed_at (timestamp, nullable)
  • status_reason (text, nullable)
  • status_expires_at (timestamp, nullable)
  • deleted_at (timestamp, nullable — SoftDeletes column)

New table: account_status_logs — stores the audit trail for status changes and admin notes.

New table: deleted_accounts — stores snapshots of deleted user rows during grace.


Upgrading to v2.3.x from v2.1.x

One breaking change if you instantiate package exceptions directly.

The constructor signature of AuthException and all subtypes changed:

// Before (v2.1.x)
new OtpInvalidException(string $message, int $code = 0, ?Throwable $previous = null);

// After (v2.3.x)
new OtpInvalidException(string $message, ?string $errorKey = null, array $replacements = [], ?Throwable $previous = null);

If you only catch package exceptions (which is the normal case), no change needed. $e->getMessage() still works.

If you throw package exceptions in your own code (unusual), update the constructor arguments.


Upgrading to v2.0.x from v1.x

Multiple breaking changes. See the v2.0.0 section below for the full list.


v2.6.0 — Current stable

Tag: v2.6.0 | Released: 2026-05-25

Phone capture + verification, full two-factor authentication (TOTP / Email / SMS) with backup codes, and a trusted-device system with time-based trust levels. New auth.2fa step-up middleware. Additive — users without 2FA enrolled see no flow changes.

Added

  • Phone number support at registration (config-driven required/optional), with a pluggable driver system: log (dev), infobip, messagecentral, twilio, firebase, and custom drivers via PhoneDriverContract + PhoneDriverManager::extend(). Per-channel (sms/voice/whatsapp) provider selection with optional fallback driver.
  • Two-factor authentication — TOTP (pragmarx/google2fa, server-rendered QR), Email OTP, and SMS OTP. Multiple methods enrollable in parallel; the user picks any at challenge time.
  • Backup codes — 8 single-use codes generated on first 2FA enrollment, HMAC-SHA256 hashed with the app key as pepper, regeneratable.
  • Login challenge flow — once 2FA is enrolled, login returns a challenge_token instead of a token; POST /auth/2fa/challenge completes it. Method switching + resend supported.
  • Trusted devices — registration device auto-trusted; time-based progression (low/medium/high); three assignment modes; revocation matrix. 2FA bypass requires both the device fingerprint and a server-issued X-Trusted-Device-Token — fingerprint alone never bypasses.
  • Require2FA middleware (auth.2fa) — GitHub-style step-up for sensitive endpoints, with block / force_enroll / password_confirm fallbacks.
  • Social profile completion — when social.profile_completion.enabled is true, a brand-new OAuth user missing the host's required fields gets a requires_profile_completion step (POST /auth/social/complete) instead of being created immediately, enforcing the same extra_fields_rules + phone rules as the email flow. No user row until completion.
  • Password sudo modePOST /auth/password/confirm grants a short step-up window for the password_confirm middleware path.
  • Request::authContext() — read-only snapshot of 2fa_enabled, 2fa_verified, trust_level, phone_verified, sudo_active.
  • auth:install --upgrade — runs only the new v2.6 migrations and prints a feature summary.
  • 8 new eventsPhoneVerified, TwoFactorEnrolled, TwoFactorDisabled, TwoFactorVerified, TwoFactorChallengeIssued, TwoFactorChallengeFailed, TrustedDeviceAdded, TrustedDeviceRevoked.

New tables

auth_two_factor_methods, auth_two_factor_backup_codes, auth_two_factor_challenges, auth_trusted_devices, auth_phone_otp_codes. New users columns: phone, phone_verified_at, two_factor_required.

New dependencies

pragmarx/google2fa: ^8.0, bacon/bacon-qr-code: ^3.0.

Changed

  • UserLoggedIn still fires at credential success even when a 2FA challenge is pending (preserves v2.5 listener semantics). TwoFactorChallengeIssued fires when the challenge is created; TwoFactorVerified fires on completion.
  • Default trusted_devices.bypass_2fa_min_level is high (the strongest trust signal) — override with AUTH_TRUST_BYPASS_MIN=medium for looser UX.

Who must upgrade

Anyone who wants phone verification, 2FA, or trusted devices. Pure additive — safe for existing v2.5 deployments. See the Upgrading to v2.6.0 from v2.5.x steps above.


v2.5.1

Tag: v2.5.1 | Released: 2026-05-22

Fixed

  • Refresh now re-validates account status. TokenService::refresh() used to mint new tokens without re-checking whether the user was still allowed to authenticate, so a suspended, disabled, or soft-deleted user could keep rotating tokens for the lifetime of the refresh window. The flow now re-runs AccountStatusService::assertCanLogin(), hasVerifiedEmail() (gated by the new verification.required_for_refresh key), and trashed() before issuing the new pair.
  • Refresh rotation is now properly atomic. Reuse detection used to read the token row outside the rotation transaction, which let two concurrent legitimate refreshes both pass the consumed check and produced "Invalid refresh token" without the family revoke. The row is now lockForUpdate-selected inside the transaction; any presentation of a consumed token revokes the whole family (RFC 6749 §10.4 strict rotation). The revoke runs after the rotation transaction commits, so its writes cannot be rolled back by the throw that follows.
  • Refresh now updates the session record. Previously, AuthService::refreshToken() rotated the Sanctum token but left auth_sessions_extended.sanctum_token_id pointing at the now-deleted old token, silently breaking /auth/sessions, session revocation, and last-active tracking after the first refresh. The session row is now re-pointed (or created if missing) and last_active_at is bumped.
  • Exception renderer honors the configured route prefix. AuthServiceProvider::isAuthRoute() used to hardcode auth/, so hosts mounted under api/v1/auth lost the JSON envelope around ValidationException and AuthenticationException. It now reads auth_system.routes.prefix; for root-mounted setups it falls back to matching named routes that start with auth..
  • Frontend magic-link URL is now validated. Setting magic_link_target=frontend with an empty or malformed frontend_verify_url / frontend_reset_url used to produce emails with broken links (?token=... and no host). The package now throws AuthConfigurationException at link-generation time so misconfigurations fail loudly in staging instead of silently in production.
  • ApiTokenAuth no longer leaks raw exception messages. Unknown exceptions during token validation used to be returned verbatim in the response body — exposing SQL fragments, file paths, or stack-trace hints. Known AuthException subclasses still pass through with their safe message; everything else is logged and replaced with a generic "Invalid API token.".
  • Browser fingerprint header is format-validated. X-Browser-Fingerprint used to be accepted verbatim (truncated to 191 chars). It must now be a hex digest within [browser_fingerprint_min_length, browser_fingerprint_max_length] characters (defaults 32–128) — otherwise it is treated as absent. The fingerprint is still advisory and must not be treated as proof of device identity.
  • GeoIP lookup no longer blocks the auth path and uses HTTPS. The third-party IP-to-location call (off by default) used to run synchronously over http:// on every login, adding up to 3s of latency per request. It now runs as the new BackfillSessionLocation queued job, which fills country/city on the session row after it is created. The default endpoint is https://ip-api.com/json/{ip} and is overridable via auth_system.device.location_endpoint.
  • Removed an unused use Mockery; statement in tests/Feature/Auth/SocialAuthTest.php that was producing a PHP warning on every test run (Mockery is in the root namespace, so the import had no effect).

Added

  • AuthConfigurationException — typed exception for programmer-facing misconfiguration. Default error key: auth_misconfigured.
  • BackfillSessionLocation job — queues GeoIP lookups off the auth request path. Dispatched only when device.resolve_location=true, the IP is public, and the session row was not pre-populated by a host-app resolver.

New optional config keys

Key Default Purpose
verification.required_for_refresh true Whether POST /auth/refresh requi...
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.
terminal42/code-quality-tools
codifyo/ts-generator-bundle
andydefer/laravel-cluster
testo/fiber
mintobit/jobqueue
a4sex/maintenance-bundle
a4sex/entity-date-update
a4sex/client-identifier
a4sex/base-utilites
a4sex/key-value-storage
a4sex/micro-status
chilldev/dependency-injection-extra
datinglibre/datinglibre-app-api
biberltd/corebundle
bricre/symfony-bundle-test
biberltd/logbundle
dominium/http-adapter-bundle
dominium/google-analytics
a4sex/auto-clean-entity
christhompsontldr/laravel-inky