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

Laravel Auth Laravel Package

joe-404/laravel-auth

Drop-in, config-driven authentication for Laravel 12 and 13: OTP and magic-link verification, login, token refresh, password reset, multi-session management, Google OAuth, API tokens, account status workflow, and referral codes via a uniform JSON API.

View on GitHub
Deep Wiki
Context7

Getting Started

1. Install and bootstrap (one sitting, ~30 min).

composer require joe-404/laravel-auth
php artisan auth:install          # publishes config/auth_system.php, migrations, lang files, mounts /auth routes
php artisan migrate

Then wire the traits into app/Models/User.php — this is non-negotiable, the package will fatal without them:

use Laravel\Sanctum\HasApiTokens;
use Spatie\Permission\Traits\HasRoles;
use Illuminate\Notifications\Notifiable;
use Illuminate\Database\Eloquent\SoftDeletes;
use Joe404\LaravelAuth\Concerns\HasAccountStatus;

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

Minimum .env for a working flow:

AUTH_MODE=both                    # token | session | both
AUTH_VERIFICATION_METHOD=both     # otp | magic_link | both
MAIL_MAILER=smtp
CACHE_STORE=redis                 # OTPs, refresh tokens, rate limits

2. Where to look first, in this order.

File / location Why it matters day-to-day
config/auth_system.php Almost every behavioural decision lives here — route prefix, OTP TTL, lockout thresholds, referral abuse rules, 2FA, SMS driver. Read it top to bottom once before writing any code.
docs/installation.md Manual steps and the troubleshooting section; the auth:install command does not cover everything in every host.
routes/ (published/mounted) The real contract. The README table is a good index, but confirm request/response shapes from the route + controller source.
Joe404\LaravelAuth\Contracts\ResponseFormatterContract The one seam you will almost certainly override.
Events list (Registered, LoggedIn, PasswordChanged, ReferralRedeemed, PhoneVerified, TwoFactorEnrolled, TrustedDeviceAdded, …) Where you attach onboarding, analytics, provisioning, and audit code — instead of touching the package.

3. First use case: get the three-step registration + login running with curl before touching your frontend.

# 1) start registration -> returns temp_token + method + expires_in (minutes)
curl -sX POST localhost/auth/register -H "Content-Type: application/json" \
  -d '{"email":"[email protected]"}'

# 2) verify the emailed OTP -> returns completion_token
curl -sX POST localhost/auth/register/verify-otp -H "Content-Type: application/json" \
  -d '{"email":"[email protected]","otp":"482910"}'

# 3) set password -> returns user + token + refresh_token
curl -sX POST localhost/auth/register/complete -H "Content-Type: application/json" \
  -d '{"completion_token":"uuid","password":"Secret123!","password_confirmation":"Secret123!"}'

# 4) authenticated ping
curl -s localhost/auth/me -H "Authorization: Bearer 1|abc..."

Every response shares one envelope, which is the single most useful fact about this package:

{ "success": true,  "message": "...", "data":   {} }
{ "success": false, "message": "...", "errors": {} }

If those four calls work end-to-end, the rest of the package is configuration and endpoint coverage rather than new concepts.


Implementation Patterns

Build one client wrapper, not fifty call sites. Because the envelope and the refresh contract are uniform, put a single HTTP client in front of /auth/* on each client (mobile, SPA, server-to-server) and let feature code call typed methods.

// token storage + envelope unwrapping + 401 refresh interceptor
const res = await http.post('/auth/login', { email, password });
store.set(res.data.data.token, res.data.data.refresh_token);

Centralising this pays off twice: when you swap ResponseFormatterContract for your house format, and when token-refresh semantics change.

Refresh-token rotation: serialise it. The package does family-level revocation on reuse detection. That means two parallel requests both hitting a 401 and both calling /auth/token/refresh with the same refresh token will look exactly like token theft — and the whole family gets revoked, logging the user out. Use a single-flight mutex/queue around refresh on the client and retry the queued requests with the new token.

let refreshing: Promise<string> | null = null;
onUnauthorized = () => (refreshing ??= doRefresh().finally(() => refreshing = null));

Two auth modes, one backend. For SPAs, switch to AUTH_MODE=session and rely on auth:sanctum stateful cookies — no token handling at all. For mobile, keep Bearer + refresh. Deciding this per-client early avoids retrofitting CSRF handling later. Don't try to run cookie auth and Bearer in the same client.

Treat "security settings" as a screen you get for free. /auth/sessions, /auth/devices, /auth/trusted-devices, /auth/2fa/methods, /auth/2fa/backup-codes, /auth/api-tokens map one-to-one onto a settings UI. Ship it in v1 — users expect "log out everywhere" and "my devices" now, and the package already stores the history (/auth/devices is permanent, survives logout; /auth/sessions is live-only).

2FA enrolment is a two-call dance with a one-shot secret.

POST /auth/2fa/enroll/{method}/start   -> provisioning data (TOTP secret/QR)
POST /auth/2fa/enroll/{method}/verify  -> enrolled + backup codes, returned ONCE

Persist backup codes to the user immediately in the UI ("download these now") — you cannot re-read them, only regenerate (which invalidates the old set). At login, a 2FA-enabled user gets a challenge_token instead of a real token; /auth/2fa/challenge, /switch and /resend are all scoped to that token. Build the client state machine around challenge_token as a distinct, short-lived credential — never store it where you store the real token.

Step-up for sensitive endpoints. Rather than hand-rolling sudo mode, apply the auth.2fa middleware to destructive routes (delete account, revoke trusted device, change password) and have the client react to its challenge response by prompting for password or a 2FA code via /auth/password/confirm. Combined with trusted devices (time-based low/medium/high trust that skips the challenge), this gives you risk-appropriate friction without custom code.

Use events as the integration layer, not overrides.

Event::listen(LoggedIn::class, RecordLoginAnalytics::class);
Event::listen(Registered::class, SendOnboardingSequence::class);
Event::listen(ReferralRedeemed::class, GrantReferralCredit::class);
Event::listen(AccountSuspended::class, RevokeAllSessions::class);

The referral system expects you to supply the reward side — bind your handler in the container per the config, and keep the anti-abuse logic (IP match, device fingerprint match) in the package where it belongs.

Admin surface is largely done. admin/users/{id}/status, /status/history, /notes, admin/api-tokens, admin/referrals behind the Spatie permission gate. Assign the configured admin role via seeder; then the only admin code you write is the Blade/React screens. Use system API tokens (/auth/admin/api-tokens, with abilities and optional expiry) for machine-to-machine and internal dashboards rather than storing a user's personal token.

Testing. Mail-fake the verification flow and assert on the JSON envelope rather than the notification body. Write one feature test per happy path plus these four adversarial ones, which you should not trust the package's own README to prove: OTP brute-force hits the per-email limiter; an expired OTP/temp token is rejected; a reused refresh token revokes the family; a suspended user is blocked mid-session, not just at login.


Gotchas and Tips

It is beta, at 2 stars, with zero dependents, and claims support for a Laravel version that does not yet exist publicly. Treat it accordingly: pin an exact version in composer.json, keep the package behind a thin adapter (one AuthService interface, one client wrapper) so a swap is a week not a quarter, and personally review the security-sensitive source — OTP comparison (constant-time? attempt counter?), magic-link signing/expiry/single-use, refresh-token hashing, and rate-limit key construction. Read CHANGELOG.md before every bump; the jump to 2.6 added 2FA, phone verification, and trusted devices, which is a large surface to absorb at once.

Route collision and prefix. Routes mount at /auth by default. That will clash with anything you already have, and /auth/password/* is a common existing path. Set routes.prefix (e.g. api/v1/auth) before the frontend team builds against it — renaming later means touching every client.

config:cache after every auth_system.php edit. Config-driven packages are notorious for "I changed it and nothing happened" — nine times out of ten it's the cached config in a container build.

Magic links are GET requests in an email. Corporate mail scanners and link prefetchers will consume a single-use link before the user clicks it. If your audience is enterprise email, default AUTH_VERIFICATION_METHOD=otp and offer magic links only as an opt-in. For mobile, the callback is a browser redirect — you need a deep-link handoff to carry the completion_token/reset_token back into the app.

Respect the expires_in the API hands you. The registration response tells you the OTP lifetime (10 minutes in the README example); don't hardcode it in the UI countdown, and don't let users sit on a temp_token or completion_token across an app restart without a resume path — these tokens are not the same thing as a session and won't refresh.

Fingerprinting and per-IP limiting need correct request metadata. Behind a load balancer or CDN, configure TrustProxies or every user shares one IP — which silently breaks per-IP rate limiting and referral anti-abuse (IP-match detection will flag legitimate referrals as self-abuse). Likewise, have mobile clients send a stable device identifier header consistently; an unstable fingerprint degrades trusted-device bypass into a 2FA prompt on every launch, and makes the device history list useless. Decide the header once and pin it in your client.

The package's middleware protects package routes, not yours. Suspended/disabled/deleted users can still hit your application endpoints unless you apply the account-status middleware to your own route groups. Add it at the same time you add auth:sanctum; retrofitting means auditing every group.

Soft-delete grace period requires the scheduler. Self-deletion is a 30-day soft delete with auto-restore/cleanup logic — that only runs if php artisan schedule:work/cron is actually live in every environment. Verify in staging; a missing scheduler turns into a GDPR conversation.

Migration collisions. auth:install publishes migrations that alter users. On a pre-existing app, read them before running — column conflicts and index-name clashes surface at migrate time, and deleted_at interacts with your existing queries if you already scope users.

Response format is your one deep customisation point. Bind ResponseFormatterContract in a service provider only if your API's envelope differs from {success,message,data|errors}. Doing it early is cheap; doing it after 40 endpoints are built on the default shape is not. Similarly, publish and edit resources/lang/en/ rather than string-matching messages — and while you're in there, check whether register/forgot leak account existence, since that's a common flaw in packages of this size.

Redis is recommended, not optional. OTP storage, refresh-token state, and rate limiting all want it. The database cache driver will work but makes the lockout and reuse-detection paths slow and, under concurrency, less reliable.

Version-pin your lockfile and add a smoke test suite that exercises register → verify → login → refresh → logout against the package. That suite is your canary for a bad upgrade, and it's also the artifact that lets you argue credibly for or against keeping the dependency at the next architecture review.

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.
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums
rlacerda83/query-parser
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings