Appearance
Magic links
Passwordless sign-in. A visitor types their email address, gets a one-time link, and following it signs them in.
On by default. Turn it off with LARASPRING_AUTH_MAGIC_LINKS=false, which removes the three routes below.
The flow
| Route name | Method | Path | What it does |
|---|---|---|---|
magic-link.request | GET | /magic-link | Renders the magic-link screen |
magic-link.send | POST | /magic-link | Emails a link, if the address is known |
magic-link.verify | GET | /magic-link/{token} | Redeems the link and signs the user in |
The screen the package asks for is magic-link; in the reference edition that is resources/js/pages/auth/magic-link.tsx, or .vue in the Vue edition.
What makes a link safe
Two independent guards, and both have to hold:
- The URL is a temporary signed route. It cannot be forged or replayed past its expiry without your
APP_KEY. The signature is the single authority on expiry, so an expired link is a 403 before the controller runs. - The token behind it is single-use. It lives in the cache and is forgotten the moment it is redeemed. Following the same link twice sends the visitor back to the request screen with a message, not into the account.
A per-process cache store breaks single-use
Single use is enforced through the cache, not a database table. With CACHE_STORE=array, or any store that is not shared across your web workers, one link can be redeemed more than once. Use Redis, Memcached, or the database store in production.
There is deliberately no table for this. The state is worthless a few minutes after it is created, and a package that ships a migration for it is a package that leaves rows behind forever.
Enumeration and throttling
An address that is not on file gets exactly the same response as one that is: the same redirect, the same status message, no error. The endpoint cannot be used to find out who has an account.
Requests are rate limited per email and IP pair:
php
'magic_links' => [
'expires' => 15, // minutes, and the expiry baked into the signature
'throttle' => [
'max_attempts' => 3,
'decay_seconds' => 60,
],
],LARASPRING_AUTH_MAGIC_LINK_EXPIRES sets expires from the environment. The floor is one minute; anything lower is clamped.
Remember me
The checkbox on the request form is carried through the link. It is stored with the token, not in the URL, so it cannot be flipped by editing the link.
Magic links and two-factor
A link proves an address. It does not prove a second factor. A user who has enrolled a second factor follows their link and lands on the challenge screen, not the dashboard. Otherwise the link would be a way around the very thing they turned on.
Changing the email
The package sends Laraspring\Auth\Notifications\MagicLinkNotification, a plain MailMessage built from translatable strings. Two ways to change it, in increasing order of effort:
- Reword it. Every line goes through
__(), so a translation entry for the string changes the copy without touching any code. - Restyle it. It uses Laravel's own notification mail template, so
php artisan vendor:publish --tag=laravel-mailrestyles it along with every other notification your application sends.
There is no hook yet for swapping the notification class itself. If you need one, it is a smaller change than it looks: the controller is the only caller.