Magic links
A magic link signs a user in from their inbox instead of a password. The user types their email address on the sign-in page, receives a link, and opening it completes the sign-in. The link only works in the browser that asked for it.
How it works
The user asks for a link
The sign-in page calls POST /login-actions/send-magic-link with the email and, optionally, remember_me. The call carries the FERRISKEY_SESSION cookie the sign-in page already holds. The answer is the same whether or not an account has that address, so the endpoint does not reveal which addresses exist.
FerrisKey stores the link
FerrisKey creates a link identifier (token_id, a UUID) and a secret of 43 random alphanumeric characters. It stores the identifier, an Argon2 hash of the secret, the expiry, and the session the request came from. The secret itself is never stored.
The email goes out
The email is rendered from the realm’s magic-link template and carries a link of the form <WEBAPP_URL>/realms/<realm_name>/authentication/magic-link?token_id=...&magic_token=....
The user opens the link
The portal’s magic_link_verify page calls GET /login-actions/verify-magic-link with the two query parameters.
FerrisKey verifies it
FerrisKey checks that the browser holds the FERRISKEY_SESSION cookie of the sign-in that asked for the link, that the link has not expired, and that the secret matches its hash. It then deletes the link and answers { "status": "Success", "url": ... }, where url carries the authorization code, and sets the SSO session cookie.
Requirements
| Setting | Default | Description |
|---|---|---|
magic_link_enabled | false | Turns magic-link sign-in on. While it is off, send-magic-link answers 400 magic_link_not_enabled |
magic_link_ttl | 15 | Minutes a link stays valid. The minimum is 1 |
magic_link_template_id | none | The email template used for the message |
In the console, the first two are on the Sign-in methods screen, or on the Login tab of Realm Settings in the Admin panel.
SMTP and a template are both required
FerrisKey sends nothing unless the realm has an SMTP configuration and a magic_link_template_id. The request still answers 200, so the user waits for a mail that never comes. FerrisKey records an email_not_sent event in SeaWatch with the reason, either SMTP_NOT_CONFIGURED or TEMPLATE_NOT_CONFIGURED. Set up the server on the SMTP tab of the Emails page of the console and assign the template there, as described in Email & Templates.
How a link fails
| Situation | Result |
|---|---|
The link is opened in a browser without the requesting FERRISKEY_SESSION cookie | 401 invalid_magic_link. The link stays valid for the original browser |
| The link was requested without a session cookie | It cannot be completed |
| The link has expired | 401 magic_link_expired, and the link is deleted |
| The secret is wrong | 401 invalid_magic_link, and the link is deleted. One wrong guess burns it |
| The user has an authenticator | 403. The link does not answer the TOTP challenge |
A required action other than verify_email is due | 403 |
The browser binding is what keeps a link read from a forwarded mail or a leaked inbox from signing in somewhere else. It has a cost for honest users: opening the link in the mail app of a phone, for a sign-in started on a laptop, fails.
A pending verify_email required action does not block a magic-link sign-in. If the client requires consent, the url in the answer leads to the consent screen first.
Security
The secret is hashed
FerrisKey stores only an Argon2 hash of the secret. A copy of the database does not give anyone a usable link.
Single use
A link is deleted once it signs someone in. Expired links are deleted when they are opened, and when a later request for the same realm creates a new link.
Expiry
A link expires after magic_link_ttl minutes. Keep the window short to limit what an unread email exposes.
The inbox is the factor
Whoever controls the user’s inbox, and can open the link in the browser that asked for it, signs in. Magic links are a poor fit when you want a second factor on top: a user who has an authenticator cannot use one at all, so give those users a password or a passkey instead.
Where it fits
Internal tools
For dashboards whose users already have a corporate mailbox, magic links remove password management. The user clicks “Send me a link”, checks their inbox, and is in.
A fallback for a forgotten password
Offer a magic link next to the password-reset flow for users who have no authenticator. They get in without choosing a new password.