# Magic Link Auth

Switch your site's login flow to passwordless magic links. Users enter their email, receive a one-time link, and click it to log in — no password required.

## Configure login mode

Set `loginMode` in `settings/auth.md`:

```yaml
---
provider: custom

# Login mode: password, magic-link
loginMode: magic-link

loginPage: /login
afterLogin: /account
afterLogout: /
---
```

| Value | Behavior |
|---|---|
| `password` | Email + password form (default) |
| `magic-link` | Email-only form, one-time login link sent by email |

## How it works

In `magic-link` mode, the built-in auth pages call `window.__sitemdAuth.requestMagicLink(email)`. Each provider handles the flow differently using its native passwordless API — no custom backend required for third-party providers.

The user experience is the same across all providers: enter email, receive a link, click to log in.

## What changes in your pages

When `loginMode: magic-link` is set, the built-in auth pages adapt automatically at runtime:

**`/login`** — shows an email field and "Send Login Link" button instead of the email+password form. After submission, replaces the form with a "check your email" confirmation.

**`/upgrade`** (purchase page) — shows email only, opens Stripe checkout in a new tab. After payment, the Stripe webhook creates the user account and sends a welcome email containing the magic link.

**`/forgot-password`** — not linked from the login page in magic link mode (there is no password to reset).

**`/account/settings`** — the change password section is not shown.

## Email template

Magic link emails use the `magic-link` template from `emails/auth/magic-link.md`. Customize it like any other email template:

```markdown
---
id: magic-link
subject: Your login link
# Variables available: {{loginUrl}}
---

Click to log in — this link expires in 30 minutes:

[Log in]({{loginUrl}})
```

See [Email Templates](/docs/email) for full template format and provider setup.

## JavaScript API

The auth runtime exposes `requestMagicLink` on `window.__sitemdAuth`:

```js
window.__sitemdAuth.requestMagicLink(email)
  .then(() => { /* show "check your email" */ })
  .catch(err => { /* show error */ })
```

The current `loginMode` value is available at runtime via `window.__sitemdAuthCfg.loginMode`. Use it in your own page scripts to conditionally show or hide password-related UI:

```js
var cfg = window.__sitemdAuthCfg || {};
if (cfg.loginMode === 'magic-link') {
  // hide password fields
}
```

## Provider support

Magic link mode works with all auth providers. Each uses its native passwordless API:

| Provider | How it works | Dashboard setup |
|---|---|---|
| `custom` | Your API implements `/auth/magic-link/request` and `/auth/magic-link/verify` endpoints (see below) | — |
| `supabase` | Calls `signInWithOtp` — Supabase sends the magic link email | Enable "Email OTP" in Authentication → Providers → Email |
| `firebase` | Calls `sendSignInLinkToEmail` — Firebase sends the email link | Enable "Email/Password" → toggle on "Email link (passwordless sign-in)" in Authentication → Sign-in method |
| `clerk` | Creates a sign-in with `email_link` strategy — Clerk sends the link | Enabled by default |
| `auth0` | Redirects to Auth0 Universal Login with passwordless email | Enable the "Email" passwordless connection in Authentication → Passwordless |

For third-party providers, the redirect URL / callback URL for your site must be allowed in the provider's dashboard settings.

## Custom provider API endpoints

When using `provider: custom`, your API must implement two endpoints:

**`POST /auth/magic-link/request`**

Body: `{ email: string }`

Generates a one-time token, stores it, and sends the email. Always returns `{ ok: true }` regardless of whether the email exists (prevents enumeration).

**`GET /auth/magic-link/verify?token=<tokenId>`**

Validates the token, creates a session, deletes the token, and redirects to:

```
{SITE_URL}/account#magic=<sessionToken>
```

The auth runtime handles the rest client-side.

## Testing magic links locally

The dev server includes a built-in auth stub for the `custom` provider. Navigate to your login redirect URL with any magic code to log in instantly:

```
http://localhost:4747/account#magic=anytoken
```

The stub accepts any exchange code — no real API or email required. See [Local Auth Testing](/docs/dev-server#local-auth-testing) for details.

## Related

- [User Auth & Gating](/docs/user-auth) — full auth setup, gating pages, user types
- [Dev Server — Local Auth Testing](/docs/dev-server#local-auth-testing) — test auth flows without a backend
- [Email Templates](/docs/email) — customizing transactional email templates
- [Purchase flow](/docs/user-auth#special-page-directories) — how auth-pages integrate with checkout