Updated 17 August 2026
Magic-link authentication: secure passwordless sign-in
auth.requestMagicLink creates a short-lived, single-use token and sends the sign-in URL through the configured email provider. auth.verifyMagicLink consumes the token in the backend and returns a session or an MFA challenge.
Request the same response for every email
await auth.requestMagicLink({
email,
});
return { message: "If that address can sign in, a link has been sent." };With allowMagicLinkSignup: false, an unknown email returns successfully without sending. Discard the method result and return the fixed response above. Own Auth limits each email to five requests in ten minutes.
Verify in the backend and create the session once
export async function verifyMagicLink(token: string) {
const result = await auth.verifyMagicLink({ token });
if (result.status === "mfa_required") {
return {
status: result.status,
challengeToken: result.challengeToken,
methods: result.methods,
expiresAt: result.expiresAt,
};
}
return {
status: result.status,
userId: result.user.id,
sessionToken: result.sessionToken,
expiresAt: result.session.expiresAt,
};
}For a complete result, put sessionToken in the application's session transport. Replace the browser URL after verification. A refresh or second click cannot reuse the consumed link.
Use HTTPS links on mobile
For iOS and Android, use an HTTPS Universal Link or App Link and pass the token to the same backend verification endpoint. See the native iOS guide or the React Native, Flutter, Ionic, and Capacitor examples.
Test the magic-link email
Use MemoryEmailProvider in automated tests to inspect the message created by auth.requestMagicLink without sending email or logging the credential URL. Keep the provider instance inside the test process and assert only the fields the flow needs.
import assert from "node:assert/strict";
import test from "node:test";
import { createOwnAuth, MemoryEmailProvider } from "own-auth";
test("creates a magic-link email", async () => {
const emailProvider = new MemoryEmailProvider();
const auth = createOwnAuth({
tokenPepper: "test-token-pepper",
baseUrl: "http://localhost:3000",
emailProvider,
});
await auth.requestMagicLink({ email: "alice@example.com" });
assert.equal(emailProvider.messages.length, 1);
const message = emailProvider.messages[0];
assert.ok(message);
assert.equal(message.to, "alice@example.com");
assert.equal(message.type, "magic_link");
const link = new URL(message.url);
assert.equal(link.origin, "http://localhost:3000");
assert.equal(link.pathname, "/auth/magic-link/verify");
assert.ok(link.searchParams.get("token"));
});