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.
For developers: this file exists so you can paste it (or the full repo snapshot from gitingest) straight into any AI assistant and get accurate, repo-aware help immediately — without explaining the project from scratch every time.
How to get a full snapshot of the repo for your AI:
- Open https://gitingest.com/joe-nassar-tech/laravel-auth
- Set Include files under to
100kB- Click Ingest
- Under the summary, click Copy All and paste into your AI — or Download as an
.mdfile and attach itThat gives the AI the actual source code of every file. Use this
AI_Context.mdwhen you want a quick conceptual briefing; use gitingest when you need the AI to read or edit code.
Package name: joe-404/laravel-auth
Packagist: composer require joe-404/laravel-auth
Type: Composer library — NOT a Laravel app, NOT a standalone service
Purpose: Drop-in, config-driven authentication library for Laravel 12/13. Installs via composer require + php artisan auth:install, then exposes a complete JSON auth API with zero frontend coupling.
The package covers everything a backend auth system needs:
auth.2fa step-up middleware with password-confirm (sudo) fallbackrequires_profile_completion step (POST /auth/social/complete) that enforces the same extra_fields_rules + phone rules as the email flow; no user row is created until completion| Layer | Technology |
|---|---|
| Language | PHP ^8.2 |
| Framework | Laravel ^12.0 | ^13.0 |
| Auth tokens | Laravel Sanctum ^4.0 |
| Roles/permissions | Spatie Laravel Permission ^6.0 |
| Social auth | Laravel Socialite ^5.0 |
| Device detection | jenssegers/agent |
| Cache/queue | Redis (recommended; any Laravel-supported driver works) |
| Testing | Pest with RefreshDatabase, Mail::fake(), Queue::fake() |
| Runtime safety | Octane/Swoole compatible — no request state on singletons |
joe-404/laravel-auth/
├── config/
│ └── auth_system.php ← Single config file; every option has an env variable
│
├── database/
│ ├── migrations/ ← 6 migration files the package owns
│ │ ├── 2024_01_01_000001_add_columns_to_users_table.php (last_login_at, is_active)
│ │ ├── 2024_01_01_000002_create_auth_otp_codes_table.php
│ │ ├── 2024_01_01_000003_create_auth_sessions_extended_table.php
│ │ ├── 2024_01_01_000004_create_auth_social_accounts_table.php
│ │ ├── 2024_01_01_000005_create_auth_api_tokens_table.php
│ │ ├── 2024_01_01_000006_create_auth_refresh_tokens_table.php
│ │ ├── 2026_05_16_000001_add_account_status_to_users_table.php (v2.4)
│ │ ├── 2026_05_16_000002_create_deleted_accounts_table.php (v2.4)
│ │ ├── 2026_05_17_000003_add_status_expires_at_to_users_table.php (v2.4)
│ │ ├── 2026_05_17_000005_create_account_status_logs_table.php (v2.4)
│ │ ├── 2026_05_20_000001_create_referrals_table.php (v2.5 — referrals)
│ │ ├── 2026_05_20_000002_add_fingerprint_hash_to_auth_sessions_extended.php (v2.5)
│ │ └── 2026_05_20_000003_create_auth_user_devices_table.php (v2.5 — permanent device history)
│ └── seeders/
│ └── AuthRolesSeeder.php ← Creates default roles via Spatie Permission
│
├── docs/
│ ├── AI_Context.md ← THIS FILE
│ ├── configuration.md ← Complete config reference (every key documented)
│ ├── customization.md ← Extra fields, transformers, referral codes
│ ├── events.md ← Event system, listeners, auto-discovery
│ ├── installation.md ← Step-by-step install, troubleshooting
│ ├── localization.md ← Multi-language, translation files
│ ├── upgrading.md ← Version migration guides
│ ├── account-status.md ← Account status system, timed bans, admin endpoints
│ ├── account-deletion.md ← Soft-delete, grace period, purge worker
│ └── referral-codes.md ← Referral system + fingerprint anti-abuse + reward handlers
│
├── resources/
│ ├── devices.json ← ~500 device model definitions for UA parsing
│ ├── lang/
│ │ ├── en/messages.php ← English success messages
│ │ └── en/errors.php ← English error messages
│ └── views/emails/ ← Blade email templates (publishable)
│
├── routes/
│ └── auth.php ← All package routes (33 routes total)
│
├── src/
│ ├── AuthServiceProvider.php ← Registers everything; mounts routes; schedules jobs
│ │
│ ├── Commands/
│ │ └── InstallCommand.php ← `php artisan auth:install`
│ │
│ ├── Concerns/
│ │ └── HasAccountStatus.php ← Trait mixed into the host User model
│ │
│ ├── Contracts/ ← 6 interfaces — the package's extension API
│ │ ├── ResponseFormatterContract.php
│ │ ├── OtpChannelContract.php
│ │ ├── CombinedOtpChannelContract.php
│ │ ├── ExtraFieldTransformerContract.php
│ │ ├── ReferralCodeGeneratorContract.php
│ │ └── DeviceResolverContract.php
│ │
│ ├── Events/ ← Dispatched at key lifecycle moments
│ │ ├── RegistrationEmailVerified.php ← verify step (SPA cross-tab handoff)
│ │ ├── EmailVerified.php ← finalize step (user row exists)
│ │ ├── UserLoggedIn.php
│ │ ├── UserLoggedOut.php
│ │ ├── PasswordChanged.php
│ │ ├── UserRegistered.php
│ │ ├── SuspiciousLoginDetected.php
│ │ ├── AccountStatusChanged.php
│ │ ├── AccountDeleted.php
│ │ ├── AccountRestored.php
│ │ └── AccountPurged.php
│ │
│ ├── Exceptions/ ← Typed exceptions (never return false to signal failure)
│ │ ├── AuthException.php ← Base exception
│ │ ├── AccountInactiveException.php
│ │ ├── EmailNotVerifiedException.php
│ │ ├── OtpInvalidException.php
│ │ ├── OtpExpiredException.php
│ │ ├── TokenExpiredException.php
│ │ └── TokenRevokedException.php
│ │
│ ├── Http/
│ │ ├── Concerns/
│ │ │ ├── RespondsWithJson.php ← Trait used by ALL controllers; resolves formatter
│ │ │ └── ResolvesMessages.php
│ │ ├── Controllers/
│ │ │ ├── RegisterController.php
│ │ │ ├── LoginController.php
│ │ │ ├── LogoutController.php
│ │ │ ├── EmailVerificationController.php
│ │ │ ├── PasswordResetController.php
│ │ │ ├── PasswordChangeController.php
│ │ │ ├── TokenRefreshController.php
│ │ │ ├── SessionController.php
│ │ │ ├── SocialAuthController.php
│ │ │ ├── ApiTokenController.php
│ │ │ ├── AccountController.php ← deactivate, delete
│ │ │ └── Admin/
│ │ │ ├── UserStatusController.php ← GET|POST /admin/users/{id}/status
│ │ │ └── UserAuditController.php ← GET history, POST notes
│ │ ├── Formatters/
│ │ │ └── DefaultResponseFormatter.php
│ │ ├── Middleware/
│ │ │ ├── AuthMode.php ← switches session vs token based on config
│ │ │ ├── ApiTokenAuth.php ← authenticates auth_at_* tokens
│ │ │ ├── RequireEmailVerified.php
│ │ │ ├── RequireActiveAccount.php ← `auth.active` — blocks suspended/disabled mid-session
│ │ │ ├── RateLimitAuth.php
│ │ │ ├── DeviceFingerprint.php
│ │ │ ├── ConditionalCsrf.php
│ │ │ ├── FeatureFlag.php
│ │ │ └── RejectRefreshToken.php
│ │ └── Requests/ ← One FormRequest per endpoint
│ │
│ ├── Jobs/
│ │ ├── CleanExpiredOtpRecords.php ← every 5 min
│ │ ├── CleanExpiredRefreshTokens.php ← hourly
│ │ ├── CleanExpiredApiTokens.php ← hourly (only when api_tokens.enabled)
│ │ ├── RevertExpiredAccountStatuses.php ← every N min (timed ban sweep)
│ │ └── PurgeExpiredAccountDeletions.php ← hourly (delete grace period)
│ │
│ ├── Listeners/
│ │ ├── SendVerificationNotification.php
│ │ └── NotifySuspiciousLogin.php
│ │
│ ├── Models/
│ │ ├── AuthOtpCode.php
│ │ ├── AuthSessionExtended.php
│ │ ├── AuthRefreshToken.php
│ │ ├── AuthSocialAccount.php
│ │ ├── AuthApiToken.php
│ │ ├── AccountStatusLog.php
│ │ └── DeletedAccount.php
│ │
│ ├── Notifications/ ← One class per email; all publishable via config
│ │
│ ├── Services/ ← All business logic lives here
│ │ ├── AuthService.php ← registration + login (the core service)
│ │ ├── OtpService.php ← OTP generation, verification, magic links
│ │ ├── TokenService.php ← Sanctum token issuance + refresh rotation
│ │ ├── ApiTokenService.php ← Long-lived API token CRUD
│ │ ├── SessionService.php ← AuthSessionExtended list + revoke
│ │ ├── DeviceService.php ← UA parsing + new-device detection
│ │ ├── RateLimitService.php ← Per-IP + per-email rate limit checks
│ │ ├── LockoutService.php ← Per-account lockout after failed logins
│ │ ├── SocialAuthService.php ← Google OAuth + account linking + profile completion (v2.6)
│ │ ├── AccountStatusService.php ← Status read/write, lazy auto-unban
│ │ ├── AccountAuditService.php ← Audit log writes (logStatusChange, logNote)
│ │ ├── AccountDeletionService.php ← Soft-delete, restore, purge
│ │ ├── DefaultReferralCodeGenerator.php
│ │ └── UniqueColumnResolver.php ← Introspects schema for unique columns to null on purge
│ │
│ └── Support/
│ └── AccountStatus.php ← Enum-like constants: ACTIVE, SUSPENDED, DISABLED, DELETED, DEACTIVATED
│
└── tests/
├── Feature/Auth/ ← End-to-end flow tests
├── Feature/Sessions/
├── Feature/ApiTokens/
├── Feature/RateLimiting/
├── Feature/Account/ ← TimedBanTest, DeactivateTest, AuditLogTest
└── Unit/Services/
| Table | Owner | Purpose |
|---|---|---|
users |
Host app (altered by package) | The main users table. Package adds: last_login_at, is_active, account_status, status_changed_at, status_reason, status_expires_at, and (v2.6) phone, phone_verified_at, two_factor_required |
auth_otp_codes |
Package | SHA-256 hashed OTP codes and magic-link UUIDs. Shared between registration and password reset |
auth_sessions_extended |
Package | One row per active session — device, browser, OS, IP, geo. Used by session list/revoke |
auth_refresh_tokens |
Package | One-time-use refresh tokens with family tracking for reuse detection |
auth_social_accounts |
Package | Links Google accounts to local users |
auth_api_tokens |
Package | Long-lived scoped API tokens (auth_at_* format) |
personal_access_tokens |
Sanctum | Standard Sanctum access tokens |
account_status_logs |
Package (v2.4) | Audit log — every status change and admin note with actor/source/comment context |
deleted_accounts |
Package (v2.4) | Permanent snapshot of the users row after soft-delete, kept even after hard-delete for FK integrity |
auth_two_factor_methods |
Package (v2.6) | Enrolled 2FA methods per user; TOTP secret encrypted via Crypt, is_default, verified_at |
auth_two_factor_backup_codes |
Package (v2.6) | Single-use recovery codes (HMAC-SHA256 with app-key pepper) |
auth_two_factor_challenges |
Package (v2.6) | Short-lived login challenges (UUID token, attempts counter, expiry) |
auth_trusted_devices |
Package (v2.6) | Trusted devices with trust level + server-issued secret_hash for 2FA bypass |
auth_phone_otp_codes |
Package (v2.6) | Hashed phone OTPs for phone verification + SMS 2FA |
roles, permissions, etc. |
Spatie Permission | Roles and permissions |
POST /auth/register
↓ validate email + extra_fields_rules
↓ cache pending registration (auth_system.password.pending_ttl_minutes)
↓ OtpService::send() → OTP + magic link (method = otp | magic_link | both)
↓ return temp_token (UUID, used for Reverb subscription)
↓
POST /auth/register/verify-otp OR GET /auth/register/verify-magic/{token}
↓ verify OTP hash OR signed URL
↓ return completion_token (UUID, 15 min TTL, stored in Redis)
↓
POST /auth/register/complete
↓ validate completion_token, password
↓ DB::transaction():
│ User::create() with email + extra fields + transformed fields
│ assign default_role via Spatie
│ generate referral_code (if enabled)
↓ dispatch EmailVerified event (host listeners run here)
↓ TokenService::issue() → Sanctum token + refresh token
↓ return user + token + refresh_token (or session cookie in web mode)
POST /auth/login
↓ validate email + password
↓ RateLimitService::check() (per IP + per email)
↓ LockoutService::check() (per account)
↓ Hash::check() against users.password
↓ AccountStatusService::assertCanLogin() — blocks suspended/disabled
↓ if status = deactivated AND auto_reactivate_on_login:
│ AccountStatusService::changeStatus(ACTIVE, source=login_auto_reactivate)
↓ if status = deleted AND within grace AND auto_restore_on_login:
│ AccountDeletionService::restore()
↓ DeviceService::resolve() — check for new device → SuspiciousLoginDetected event
↓ AuthSessionExtended::create() — record session row
↓ dispatch UserLoggedIn event
↓ TokenService::issue() → access + refresh tokens (or session)
↓ return user + token + refresh_token
auth.active middleware (per-request enforcement)Every request to a protected route (fan/* or admin/*):
↓ RequireActiveAccount middleware
↓ AccountStatusService::current($user)
↓ revertIfExpired() — lazy auto-unban check
│ if suspended AND status_expires_at < now():
│ changeStatus(ACTIVE, source=auto_unban_lazy)
↓ if status in login_blocked → 403 with per-status error key
↓ Continue to controller
RevertExpiredAccountStatuses job (every N minutes, scheduled by AuthServiceProvider):
↓ WHERE account_status IN temporary_statuses AND status_expires_at < now()
↓ For each: AccountStatusService::revertIfExpired($user, 'auto_unban_sweep')
↓ changeStatus(ACTIVE) → revoke_sessions=false (user is already out)
↓ AccountAuditService::logStatusChange(source=auto_unban_sweep)
↓ dispatch AccountStatusChanged event
POST /auth/token/refresh
↓ find AuthRefreshToken by hash — 401 if not found
↓ DB::transaction() + SELECT FOR UPDATE (atomic)
↓ if already consumed → reuse detected → revoke entire family → 401
↓ mark old refresh token consumed
↓ revoke paired Sanctum access token
↓ issue new access token + new refresh token (same family)
↓ return new token pair
Everything lives in config/auth_system.php. The file is published to the host app via php artisan vendor:publish --tag=auth-config. Every key that reads env() can be overridden via .env. Keys that don't read env() must be edited directly in the published config file.
Full documentation: docs/configuration.md — covers every single key with explanation, valid values, and examples.
Quick overview of top-level sections:
| Section | What it controls |
|---|---|
mode |
api / web / both — how credentials are issued |
spa_token |
Whether browser clients get a token instead of a cookie in both mode |
require_email_verification |
Block login until email is verified |
routes |
register (auto-mount on/off), prefix (URL prefix), middleware (override stack) |
registration |
extra_fields_rules, extra_fields_messages, extra_fields_transformers, request_class |
referral_code |
Auto-generate unique referral codes per user |
verification |
OTP length/expiry, magic link target, frontend URLs |
password_reset |
Override verification method for password resets |
token_ttl |
Access + refresh token lifetimes per client type (mobile / spa / api) |
rate_limits |
Per-endpoint rate limit strings ("max:decay_minutes") |
password |
Min length, uppercase/number/special requirements, pending TTL |
roles |
Default role for new users, seeded roles |
otp_channel |
email or FQCN of a custom OtpChannelContract implementation |
mail |
Override individual notification classes; toggle lifecycle notifications on/off |
social |
Google OAuth credentials + frontend redirect URL + profile-completion toggle (v2.6) |
reverb |
Enable WebSocket real-time verification push |
api_tokens |
Enable long-lived API token system |
queue |
Queue connection + name for maintenance jobs |
response |
Custom response formatter class |
security |
New-device alerts, lockout settings |
account.status |
Status column, allowed statuses, login-blocked list, auto-unban |
account.deletion |
Self-service delete, grace period, purge behaviour |
account.deactivation |
Self-service pause, auto-reactivate on login |
account.audit |
Audit log table, retention, notes endpoint, history endpoint |
errors |
Override any error message string by key |
messages |
Override any success message string by key |
The package exposes 6 PHP interfaces. Implement any of them to replace a piece of built-in behaviour without forking the package.
| Interface | Located at | Registered via | Replaces |
|---|---|---|---|
ResponseFormatterContract |
src/Contracts/ |
response.formatter config or app()->bind() |
JSON envelope structure |
OtpChannelContract |
src/Contracts/ |
otp_channel.driver config |
OTP/magic-link delivery (email by default) |
CombinedOtpChannelContract |
src/Contracts/ |
(extends OtpChannelContract) | Single-message combined OTP + link |
ExtraFieldTransformerContract |
src/Contracts/ |
registration.extra_fields_transformers config |
Derive/normalize a field from validated registration input |
ReferralCodeGeneratorContract |
src/Contracts/ |
referral_code.generator config |
Referral code generation algorithm |
DeviceResolverContract |
src/Contracts/ |
app()->bind() in AppServiceProvider |
User-Agent / device fingerprint parsing |
The package dispatches events and never calls host-app code directly. Host apps listen to whatever events they care about using standard Laravel auto-discovered listeners in app/Listeners/.
| Event class | Namespace | Fired when | Payload |
|---|---|---|---|
RegistrationEmailVerified |
Joe404\LaravelAuth\Events\ |
OTP / magic link verified at step 2, before user row exists. Broadcasts on private-auth.verification.{tempToken} for SPA cross-tab handoff. |
$tempToken, $completionToken, $email |
EmailVerified |
Joe404\LaravelAuth\Events\ |
Registration step 3 succeeds | $user, $tempToken |
UserLoggedIn |
Joe404\LaravelAuth\Events\ |
Successful login | $user, $request |
UserLoggedOut |
Joe404\LaravelAuth\Events\ |
Any logout | — |
PasswordChanged |
Joe404\LaravelAuth\Events\ |
Password reset or change | $user |
SuspiciousLoginDetected |
Joe404\LaravelAuth\Events\ |
Login from new device | $user, $ip, $browser, $os, $city, $country |
AccountStatusChanged |
Joe404\LaravelAuth\Events\ |
Any status change | $user, $from, $to, $source |
AccountDeleted |
Joe404\LaravelAuth\Events\ |
Soft-delete initi... |
How can I help you explore Laravel packages today?