Magic Links
Enable passwordless authentication via email magic links in HelloJohn. Setup, SMTP configuration, customization, and SDK integration.
Magic links let users sign in with a single click from their email — no password required. HelloJohn sends a time-limited link that authenticates the user when they click it.
How it works
1. User enters their email address
2. HelloJohn generates a one-time token and sends a magic link via email
3. User clicks the link (valid for 15 minutes by default)
4. HelloJohn validates the token and issues access + refresh tokens
5. SDK stores tokens — user is authenticatedThe link is single-use. If the user clicks it twice, the second click returns an error.
Setup
1. Configure SMTP
Magic links require email delivery. Add your SMTP credentials to your environment:
SMTP_HOST=smtp.example.com
SMTP_PORT=587
SMTP_USER=noreply@example.com
SMTP_PASSWORD=your-smtp-password
SMTP_FROM=HelloJohn <noreply@example.com>
SMTP_TLS=trueFor local development, use Mailpit or Mailtrap:
SMTP_HOST=localhost
SMTP_PORT=1025
SMTP_USER=
SMTP_PASSWORD=
SMTP_TLS=false2. Enable magic links in the dashboard
Go to Authentication → Magic Links in the admin panel and toggle it on.
Or via API:
curl -X PATCH https://your-instance.hellojohn.dev/v2/admin/auth/config \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{"magic_link_enabled": true}'3. Configure token expiry (optional)
MAGIC_LINK_TTL=900 # seconds (default: 15 minutes)SDK integration
import { useAuth } from '@hellojohn/react'
function LoginForm() {
const { sendMagicLink } = useAuth()
const [email, setEmail] = useState('')
const [sent, setSent] = useState(false)
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()
await sendMagicLink(email)
setSent(true)
}
if (sent) {
return <p>Check your email — a login link is on the way.</p>
}
return (
<form onSubmit={handleSubmit}>
<input
type="email"
value={email}
onChange={e => setEmail(e.target.value)}
placeholder="you@example.com"
required
/>
<button type="submit">Send magic link</button>
</form>
)
}'use client'
import { useHelloJohn } from '@hellojohn/nextjs'
export function MagicLinkForm() {
const { sendMagicLink } = useHelloJohn()
async function action(formData: FormData) {
const email = formData.get('email') as string
await sendMagicLink(email, {
redirectTo: '/dashboard' // where to go after clicking the link
})
}
return (
<form action={action}>
<input name="email" type="email" required />
<button type="submit">Send link</button>
</form>
)
}Handle the callback in app/auth/magic-link/route.ts:
import { handleMagicLink } from '@hellojohn/nextjs/server'
export const GET = handleMagicLink({
redirectTo: '/dashboard',
onError: (error) => redirect('/login?error=invalid_link')
})const { HelloJohn } = require('@hellojohn/js')
const hj = new HelloJohn({ instanceUrl: 'https://your-instance.hellojohn.dev' })
// Send the magic link
await hj.auth.sendMagicLink('user@example.com', {
redirectTo: 'https://yourapp.com/auth/callback'
})
// Handle the callback (in your callback route)
const { session } = await hj.auth.verifyMagicLink(token)
// token comes from the ?token= query param in the magic link URL// Send
await hj.auth.sendMagicLink(email, { redirectTo })
// Verify (in your callback endpoint)
app.get('/auth/magic', async (req, res) => {
const { token } = req.query
const { session, user } = await hj.auth.verifyMagicLink(token)
req.session.token = session.accessToken
res.redirect('/dashboard')
})REST API
Send magic link:
POST /v2/auth/magic-link/send
Content-Type: application/json
{
"email": "user@example.com",
"redirect_to": "https://yourapp.com/auth/callback"
}Response 200 OK:
{ "message": "Magic link sent" }Verify token:
POST /v2/auth/magic-link/verify
Content-Type: application/json
{
"token": "ml_xxxxxxxxxxxx"
}Response 200 OK:
{
"access_token": "eyJ...",
"refresh_token": "rt_...",
"expires_in": 900,
"user": { "id": "usr_...", "email": "user@example.com" }
}Customizing the email template
Magic link emails can be customized from the admin panel under Emails → Templates → Magic Link.
Available variables:
| Variable | Value |
|---|---|
{{.UserEmail}} | Recipient's email address |
{{.MagicLinkURL}} | The full magic link URL |
{{.AppName}} | Your application name |
{{.ExpiresIn}} | Expiry time (e.g. "15 minutes") |
Default template is HTML with a plain-text fallback. You can upload a fully custom HTML template.
Per-tenant configuration
Override magic link settings per tenant:
PATCH /v2/admin/tenants/{tenantId}/auth/config
{
"magic_link_enabled": true,
"magic_link_ttl": 600
}Magic links work best for low-frequency apps (internal tools, dashboards) where users may not remember their password. For high-frequency consumer apps, email + password or OAuth are usually better choices.
Security considerations
- Links are single-use — clicking a valid link invalidates it immediately
- Links expire after 15 minutes by default (configurable)
- HelloJohn rate-limits magic link requests per email address (5 per hour by default)
- The token is a cryptographically random 32-byte value — not guessable
- Links are bound to the
redirect_toURL set at send time — the callback cannot be changed
OAuth / Social Login
Add Google, GitHub, Apple, Microsoft, Discord, and 4 more OAuth providers to HelloJohn. Setup instructions, callback URLs, and SDK integration for all 9 providers.
API Keys (M2M)
Create and manage API keys for machine-to-machine authentication in HelloJohn. CLI tools, backend services, webhooks, and CI/CD pipelines.