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 Magic Login Laravel Package

maize-tech/laravel-magic-login

Laravel Magic Login adds passwordless authentication to your Laravel app using temporary signed magic links sent by email. Users log in with one click, no password required, with configurable expiry and easy customization.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and publish

composer require maize-tech/laravel-magic-login
php artisan vendor:publish --provider="Maize\MagicLogin\MagicLoginServiceProvider"
php artisan migrate

Publish both the config and the migration — the package needs a table to persist issued tokens, otherwise link generation will fail at runtime (not at install time), which is a common first-day surprise.

Wire it into your authenticatable model

Add the package trait to the model you want to log in (usually App\Models\User, but any Authenticatable works):

use Maize\MagicLogin\Concerns\HasMagicLogin;

class User extends Authenticatable
{
    use HasMagicLogin;
}

Where to look first, in this order

  1. config/magic-login.php — token lifetime, route prefix/middleware, guard, redirect target, and whether package routes are registered at all.
  2. The published migration — check the expires_at column type and indexes; if you need per-guard scoping you'll be adding a column here.
  3. The package routes file (registered by the service provider) — confirm the request-link and consume-link endpoints and their names before you build UI around them.
  4. The trait — it's the extension surface for custom token generation/validation if you need to override behaviour.

First use case

Replace a "forgot password" flow, or bolt passwordless login onto an existing app without touching your password auth at all: keep Auth::attempt() for normal logins, and add a "email me a login link" endpoint next to it. The user clicks the link, the token is consumed, and they land authenticated. This is the lowest-risk adoption path because it's additive — no changes to the users table and no change to existing guards.

Implementation Patterns

Request-a-link endpoint + mail

Keep the "send link" action behind your own controller or form request rather than exposing the model call directly. That gives you a single place for rate limiting, user lookup policy (don't leak which emails exist), and audit logging:

public function __invoke(MagicLinkRequest $request)
{
    $user = User::where('email', $request->email)->first();

    // Always return the same response — don't confirm account existence.
    if ($user) {
        $user->sendMagicLoginLink(); // package-provided; check trait for exact name
    }

    return back()->with('status', 'If that account exists, a link is on its way.');
}

Customise the mail without forking

The default notification is intentionally plain. Swap in your own by extending/replacing the notification class in config, or by overriding the trait method that dispatches it. Always implements ShouldQueue on your replacement — sending synchronously inside the login request is the most common cause of slow passwordless endpoints.

Disable package routes and own the endpoints

For anything beyond a prototype, set the route-registration flag to false in the config and declare routes yourself. You keep your app's middleware stack (throttle, locale, tenant resolution), and you can version the URLs:

Route::middleware(['throttle:6,1'])->group(function () {
    Route::post('/login/link', SendLoginLink::class)->name('login.link');
    Route::get('/login/link/{token}', ConsumeLoginLink::class)->name('login.consume');
});

Multi-guard / multi-model setups

If you have admin and user guards, issue and consume tokens per guard. The config's guard setting is global, so either run two config contexts or add a guard (or model_type) column to the tokens table and resolve the guard at consumption time. Without this you'll authenticate an admin into the web guard and get silent redirect loops.

Post-login destination

Prefer a configurable redirect plus Laravel's intended() fallback so a user who was bounced from a deep link returns there after clicking the email. Hardcoding /home in the config is the usual first thing you'll change.

Testing

Notification::fake();

$this->post('/login/link', ['email' => $user->email])->assertSessionHasNoErrors();
Notification::assertSentTo($user, MagicLoginNotification::class);

$token = MagicLoginToken::where('user_id', $user->id)->latest()->first();

$this->get(route('login.consume', $token->token))
    ->assertRedirect();
$this->assertAuthenticatedAs($user);

// Single-use assertion
$this->get(route('login.consume', $token->token))->assertForbidden();

Use $this->travel(31)->minutes() to test expiry rather than sleeping or mocking the clock.

Housekeeping

Schedule pruning of expired tokens (MagicLoginToken::where('expires_at', '<', now())->delete() in a daily job). The table grows monotonically otherwise, and on high-traffic apps it's a real index-bloat problem within months.

Gotchas and Tips

  • Tokens in URLs leak. They land in web-server access logs, proxy logs, Referer headers on the landing page, and browser history. Mitigate with a short TTL (5–15 minutes), strict single-use, and a landing page that redirects immediately to a token-free URL.
  • Set Referrer-Policy: no-referrer (or strict-origin-when-cross-origin) on the consumption route if the post-login page loads any third-party assets.
  • Never issue a link for an unverified or soft-deleted user without an explicit decision. Add the check in your own controller — the package doesn't know your account-state rules.
  • Guard mismatch is the #1 bug report. If auth:api/auth:admin is your real guard but the config says web, login "succeeds" and the user still appears logged out. Verify Auth::guard(...)->check() in a test, not by eye.
  • route:cache and config:cache will hide newly installed routes and config changes. Run php artisan optimize:clear before debugging a missing endpoint.
  • Migration publishing tags — after a major upgrade, diff the published migration against the package's stub before assuming it's unchanged. Re-publishing overwrites; check the diff, don't blind-copy.
  • Token comparison — if you override validation, use hash_equals() or compare hashed tokens in the query (constant-time, and avoids timing oracles). Never == on raw token strings.
  • Expired or already-used tokens should redirect to the request form with a friendly message, not a 403 or a stack trace. Users click old emails constantly; make that path graceful.
  • Throttle link requests per email and per IP. Per-IP alone lets an attacker mail-bomb a single victim from distributed IPs; per-email alone lets one IP enumerate accounts.
  • Queue the notification and set a sane retry. If mail delivery fails silently you have no error surface — log the dispatch and expose it in your existing failed-jobs monitoring.
  • Extension points worth knowing: the token model (swap for your own via config/container binding), the notification class, and the trait methods for generation/validation. Overriding the trait is cleaner than forking the package, and survives upgrades.
  • Upgrading to 2.4.0: this release is purely compatibility and CI maintenance — it adds Laravel 13 support and bumps three GitHub Actions versions. No breaking changes, no config or migration changes, no new features. Bump the constraint, run your test suite, and you're done. The practical gate is your own app's PHP version and laravel/framework constraint, not the package.
  • Laravel 13 note: if you're on Laravel 11/12, this release changes nothing for you but does mean the package is actively tracking framework releases — worth keeping in your dependency-update rotation rather than pinning.
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