Plugins

Magic Link

Passwordless email sign-in with one-time links.

The magic link plugin emails the user a signed, short-lived URL. When they open it, Kernia consumes the token, creates or links the account, and issues a session — no password required.

Installation

Add the plugin to your server

Pass magic_link() to your Kernia config and supply a send_magic_link callback. The callback receives the recipient, the verification URL, and the raw token — send the email however you like (Resend, SES, Postmark, SMTP).

auth.py
from kernia import KerniaOptions
from kernia.auth import init
from kernia.plugins.magic_link import magic_link

async def send_magic_link(email: str, url: str, token: str) -> None:
    await email_client.send(
        to=email,
        subject="Sign in to Acme",
        html=f'<a href="{url}">Click to sign in</a>',
    )

auth = init(KerniaOptions(
    database=adapter,
    secret=os.environ["KERNIA_SECRET"],
    base_url=os.environ["KERNIA_BASE_URL"],
    plugins=[magic_link()],
    advanced={"magic-link": {"send_magic_link": send_magic_link}},
))

Add the client plugin

Kernia speaks the Better Auth wire protocol, so the official JavaScript client works unchanged. Add the magicLink client plugin:

auth-client.ts
import { createAuthClient } from "better-auth/client";
import { magicLinkClient } from "better-auth/client/plugins";

export const authClient = createAuthClient({
  baseURL: "/api/auth",
  plugins: [magicLinkClient()],
});

Usage

await authClient.signIn.magicLink({
  email: "user@example.com",
  callbackURL: "/dashboard",   // where to land after the link is verified
});

The user receives the email. When they click the link, the browser hits /api/auth/magic-link/verify, Kernia sets the session cookie, and redirects to callbackURL.

Verify manually

If you handle the redirect yourself (e.g. a native-app deep link), verify the token explicitly:

await authClient.magicLink.verify({ query: { token } });

Options

Configure under advanced["magic-link"]:

OptionTypeDefaultDescription
send_magic_linkasync (email, url, token) -> NoneRequired. Delivers the link.
expires_inint300Token lifetime in seconds.
disable_sign_upboolFalseReject links for emails with no existing account.

Schema

The plugin reuses the core verification table — no migration beyond the core schema is required.

Tokens are single-use and consumed atomically on verify, so a leaked link can't be replayed after the first click.