You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: CHANGELOG.md
+15Lines changed: 15 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -7,6 +7,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
8
8
## [Unreleased]
9
9
10
+
### Changed
11
+
-**BREAKING: Magic-link authentication rebuilt as stateful, single-use tokens** — replaces the stateless signed-URL flow. Opaque, high-entropy tokens are stored **hashed** (SHA-256) in the new `magic_link_tokens` table; only the plain token ever leaves the app (in the emailed URL).
12
+
-**Single-use by deletion** — the token row is deleted on successful redemption, so a link can never be replayed. Generating a new link **invalidates the user's previous link** for that channel.
13
+
-**Config-driven, extensible channels** — new `magic_link.channels` config (`web`, `mobile`, and any host-defined channel such as `desktop`, added by config with no code change). A channel with a `scheme`/`universal_link` is built as a deep link; otherwise `base_url` + `path`. Send accepts a `channel` parameter.
14
+
-**Optional browser/device binding** (`magic_link.bind_to_browser`) and **confirmation-required flow** (`magic_link.require_confirmation`) to defeat email-scanner auto-consumption.
15
+
-**Single route for login + confirmation** — `GET|POST /neev/loginUsingLink` (API) and `GET|POST /login-link/verify` (Blade): GET opens the link (validate-only when confirmation is required), POST is the explicit confirm. New `GET|POST /neev/loginUsingLink/validate` checks a token without consuming it. Redemption routes are now rate-limited (`throttle:10,1`).
16
+
-**New `magic_link` config block** — single engine (no driver switch): `expires_in` (default 10 min), `bind_to_browser`, `require_confirmation`, `channels`. The legacy `url_expiry_time` no longer governs magic links (still used by password-reset / email-verification links).
17
+
-**API surface** — `MagicLinkManager` service (inject it; no facade). `forWeb()`/`forMobile()`/`generate()` return an array (`url`, `token`, `expires_in`, `channel`, `model`); `validate()`/`consume()` return a `MagicLinkResult` whose status is a string constant (`MagicLinkResult::VALID`, `EXPIRED`, `INVALID`, `BINDING_MISMATCH`, `PENDING_CONFIRMATION`, `INACTIVE_USER`). Token mechanics (`generateToken`/`hashToken`/`findByToken`) live on the `MagicLinkToken` model.
18
+
-**Removed the legacy web route**`GET /login/{id}` (`login.link`); the Blade flow now redeems at `GET|POST /login-link/verify` (`login.link.verify`).
19
+
- Note: a valid magic link still completes login **without** enforcing MFA (unchanged, by design).
20
+
21
+
### Added
22
+
-**Lifecycle events** — `MagicLinkGenerated`, `MagicLinkConsumed`, `MagicLinkRejected` for auditing, notifications, and abuse detection.
Copy file name to clipboardExpand all lines: docs/spa-authentication.md
+5-2Lines changed: 5 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -256,10 +256,13 @@ POST /neev/sendLoginLink
256
256
{ "message": "Login link has been sent." }
257
257
```
258
258
259
-
The emailed link points at your frontend (`{APP_URL}/login-link?id=…&signature=…&expires=…`). Your SPA route at `/login-link` forwards the query string to the API **via XHR** (the XHR carries your stateful `Origin`, which is what triggers the cookie):
259
+
The emailed link points at your frontend (`{base_url}/login-link?token=…`, where
260
+
`base_url` is `magic_link.channels.web.base_url` — set it to your frontend origin).
261
+
Your SPA route at `/login-link` forwards the opaque `token` to the API **via XHR**
262
+
(the XHR carries your stateful `Origin`, which is what triggers the cookie):
260
263
261
264
```http
262
-
GET /neev/loginUsingLink?id={id}&expires={timestamp}&signature={signature}
0 commit comments