Skip to main content

Quick start

Out of the box the package registers a complete browser flow under the web middleware group. No controllers to write, no views to build.

The routes you get

MethodURINamePurpose
GET/magic-linkemail-magic-link.request.form"Enter your email" form
POST/magic-linkemail-magic-link.requestIssue a link or code
GET/magic-link/verify/{token}email-magic-link.confirmInert, signed confirmation page
POST/magic-link/verify/{token}email-magic-link.consumeConsume a magic link
GET/magic-link/codeemail-magic-link.code.formEnter a one-time code
POST/magic-link/codeemail-magic-link.code.consumeConsume a one-time code
GET/magic-link/resend-countdown.jsemail-magic-link.resend-countdown-scriptThe resend countdown's client script, a same-origin file

Point your "log in" link at route('email-magic-link.request.form') and you have passwordless login. A user enters their email, receives a link, clicks it, confirms, and is signed in.

The middleware, signing and rate limiting on each route are in the route reference. Move the whole flow behind a prefix, or change its middleware, with the routes.* keys in Configuration.

Because consumption is POST-only, the user clicks the emailed link (a GET) and then clicks "Sign in" on the confirmation page. That second click is the price of being safe against link-following security scanners and prefetch — tools that would otherwise spend a single-use token before the person ever sees it. We consider that trade-off worth it; it is the whole point of the package.

For first-party SPA or mobile clients that exchange the token over JSON without an interstitial, set api.enabled = true and send Accept: application/json. See The JSON contract.

What happens on a successful sign-in

  1. The POST claims the token in a single atomic, race-free conditional update.
  2. If the Fortify bridge is active and the user has confirmed two-factor authentication, the request is handed to Fortify's challenge without logging anyone in — see The two-factor handoff.
  3. Otherwise the user is logged in with Auth::login, the session id is regenerated, and the request is redirected to the originally intended URL (or routes.redirect_to).

Every step fires an event you can listen to; the payloads are in the event reference.

Next