Passwordless Sign-In with Fortify Two-Factor Support in Laravel
Laravel Composer Pacakge #passwordless #authentication #magic-link #fortify #laravel-package

Passwordless Sign-In with Fortify Two-Factor Support in Laravel

3 min read Mohamed Said Mohamed Said

Passwordless Sign-In with Fortify Two-Factor Support in Laravel

The Email Magic Link for Laravel package (pushery/email-magic-link-for-laravel) brings passwordless authentication to Laravel 13 applications. Users sign in via an emailed link or a one-time code, and the package integrates cleanly alongside Laravel Fortify without adding any runtime dependencies beyond the framework itself.

The classic magic-link problem is that security appliances pre-fetch every URL in an email, burning the single-use token before the user ever clicks it. This package solves that with a two-step flow:

  1. A GET to /magic-link/verify/{token} renders a signed confirmation page — it changes nothing.
  2. An explicit POST from that page consumes the token — something an email scanner will never issue.

Tokens are never stored in the clear. Only a keyed HMAC-SHA256 hash is persisted, consumption uses a race-free conditional claim, and each token has its own brute-force lockout (max_attempts_per_token, default 5). Responses are identical whether or not the email address exists, preventing user enumeration.

Fortify Two-Factor Handoff

If a user has confirmed TOTP through Fortify, verifying their magic link does not log them in directly. Instead, they are routed to Fortify's two-factor challenge while still unauthenticated — there is no path that trades a link click for a bypassed second factor.

For SPA and mobile clients, set api.enabled to true and the endpoint returns a JSON response indicating which branch was taken:

{ "authenticated": false, "two_factor": true, "redirect": "<challenge url>" }

The EmailMagicLink facade lets you mint credentials without sending anything, which is useful when delivering over SMS, push notifications, or a custom mailable:

use EmailMagicLink\Facades\EmailMagicLink;

$link = EmailMagicLink::issueLink($user);
$link->url;              // Signed confirmation URL
$link->expiresAt;        // Carbon instance
$link->expiresInMinutes;

$code = EmailMagicLink::issueCode($user);
$code->code;
$code->expiresAt;

You can also inject the EmailMagicLink\Contracts\MagicLinkIssuer contract directly. Rebinding MagicLinkAuthenticator controls post-verification behaviour, and implementing CaptchaGuard adds a CAPTCHA to the confirmation form.

Resend Limiting

Passwordless flows invite users to hammer the resend button. The bundled ResendGuard applies escalating cooldowns (30 s, 60 s, 120 s) plus a rolling cap of five sends per hour:

use EmailMagicLink\Contracts\ResendGuard;

public function resend(Request $request): Response
{
    $decision = $this->guard->attempt('custom-key');

    if (! $decision->allowed) {
        return back()->with('retry_after', $decision->retryAfterSeconds);
    }

    // Send mail…
}

Expired tokens are cleaned up by a scheduled command:

Schedule::command('email-magic-link:purge')->daily();

Installation

The package requires PHP 8.4 and Laravel 13. Fortify is optional.

composer require pushery/email-magic-link-for-laravel
php artisan email-magic-link:install
php artisan migrate

Key Takeaways

  • Two-step link flow prevents email scanners from consuming single-use tokens.
  • HMAC-SHA256 hashed tokens with per-token brute-force lockout protect at rest.
  • Fortify TOTP handoff ensures magic links cannot bypass a configured second factor.
  • Mint API lets you issue links or codes for SMS, push, or custom mailables.
  • ResendGuard applies escalating cooldowns and a rolling hourly send cap.
  • JSON API mode supports SPA and mobile clients, including alternate guards.

Source: Passwordless Sign-In with Fortify Two-Factor Support in Laravel — Laravel News

Found this useful?

Frequently Asked Questions

3 questions
Q01 How does Email Magic Link prevent email scanners from consuming the token?
The package splits verification into two steps. The GET request to the verify URL only renders a confirmation page and does not consume the token. The token is consumed only by an explicit POST from that page, which email security scanners do not issue.
Q02 Does the package bypass Laravel Fortify's two-factor authentication?
No. If a user has confirmed TOTP through Fortify, clicking the magic link routes them to Fortify's two-factor challenge while still unauthenticated. There is no code path that skips the second factor.
Q03 Can I use the package to send magic links via SMS or push notifications instead of email?
Yes. The EmailMagicLink facade exposes issueLink() and issueCode() methods that mint credentials without sending anything, so you can deliver them over any channel you choose.

Continue reading

More Articles

View all