Skip to content

Guide: Magic links

Passwordless sign-in via email. See examples/magic_link/.

Install

pip install "sreekarnv-fastauth[standard,email]"

.env

SECRET=...              # `fastauth generate-secret`
DATABASE_URL=sqlite+aiosqlite:///./magic_links.db
SMTP_HOST=...
SMTP_PORT=587
SMTP_USER=...
SMTP_PASS=...
SMTP_FROM=noreply@example.com
BASE_URL=http://localhost:8000

main.py

import os
from contextlib import asynccontextmanager
from dotenv import load_dotenv
from fastapi import FastAPI
from fastauth import FastAuth, FastAuthConfig
from fastauth.adapters.sqlalchemy import SQLAlchemyAdapter
from fastauth.email_transports.smtp import SMTPTransport
from fastauth.providers.magic_links import MagicLinksProvider

load_dotenv(".env")

adapter = SQLAlchemyAdapter(engine_url=os.environ.get("DATABASE_URL", "sqlite+aiosqlite:///./magic_links.db"))

email_transport = SMTPTransport(
    host=os.environ["SMTP_HOST"],
    port=int(os.environ.get("SMTP_PORT", "587")),
    username=os.environ["SMTP_USER"],
    password=os.environ["SMTP_PASS"],
    from_email=os.environ["SMTP_FROM"],
    use_tls=False,                               # disable for Mailpit / dev SMTP
)

config = FastAuthConfig(
    secret=os.environ["SECRET"],
    providers=[MagicLinksProvider()],
    adapter=adapter.user,
    token_adapter=adapter.token,
    email_transport=email_transport,
    base_url=os.environ.get("BASE_URL", "http://localhost:8000"),
)

auth = FastAuth(config)

@asynccontextmanager
async def lifespan(app: FastAPI):
    await adapter.create_tables()
    yield

app = FastAPI(lifespan=lifespan)
auth.mount(app)

Try it

uvicorn main:app --reload

curl -X POST http://localhost:8000/auth/magic-links/login \
  -H "Content-Type: application/json" \
  -d '{"email":"a@b.com"}'
# -> {"message":"Magic link sent — check your email"}

# If unknown, a user was auto-created. Click the link in the email (or via Mailpit):
# http://localhost:8000/auth/magic-links/callback?token=...
# (cookie mode sets the cookies; JSON mode returns a token pair)

Local dev with Mailpit

mailpit is a great local SMTP catcher:

mailpit              # listens on :1025 (SMTP) and :8025 (web UI)

# In .env use:
SMTP_HOST=localhost
SMTP_PORT=1025
SMTP_USER=           # empty
SMTP_PASS=           # empty
SMTP_FROM=noreply@localhost

View the email at http://localhost:8025. Set use_tls=False.

Notes

  • Magic links auto-register unknown emails with hashed_password=None. Gate via allow_signin(user, "magic_link") if account creation should be restricted.
  • token_adapter is required — MagicLinksProvider.send_login_request raises ConfigError without it.
  • on_signin is JSON-mode only. See Hooks.