Configure Authentication
Magic Links
Passwordless sign-in via email — users click a link to authenticate without a password.
Magic links let your users sign in without a password. They enter their email address, receive a link in their inbox, and click it to authenticate. There are no credentials to remember and no passwords to manage — just a simple, secure flow that works for both new and returning users.
Enable Magic Links
Via the Dashboard
The quickest way to turn on magic links is through your Banata dashboard:
- Go to Authentication > Methods.
- Toggle Magic Link to enabled.
- Navigate to Emails > Providers and make sure you have an active email provider configured (this is required so Banata can send the magic link emails).
- Optionally, customize the
magic-linktemplate under Email Templates.
Via the SDK
You can also enable magic links programmatically:
await banata.configuration.saveDashboardConfig({
authMethods: {
magicLink: true,
},
});Client-Side API
Once magic links are enabled, you can trigger them from your frontend using authClient.signIn.magicLink:
import { authClient } from "@/lib/auth-client";
const { error } = await authClient.signIn.magicLink({
email: "user@example.com",
callbackURL: "/dashboard", // Where to redirect after sign-in
});
if (error) {
// Handle errors:
// - "Rate limit exceeded" (429)
// - "Invalid email" (422)
console.error(error.message);
} else {
// Show "Check your email" message
}Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
email | string | Yes | The user's email address |
callbackURL | string | No | Where to redirect after successful sign-in |
Complete Form Example
Here is a full React component that handles the magic link sign-in flow, including loading state, error handling, and a confirmation screen:
"use client";
import { authClient } from "@/lib/auth-client";
import { useState } from "react";
export default function MagicLinkSignIn() {
const [email, setEmail] = useState("");
const [sent, setSent] = useState(false);
const [loading, setLoading] = useState(false);
const [error, setError] = useState("");
async function handleSubmit(e: React.FormEvent) {
e.preventDefault();
setError("");
setLoading(true);
const { error } = await authClient.signIn.magicLink({
email,
callbackURL: "/dashboard",
});
setLoading(false);
if (error) {
setError(error.message ?? "Failed to send magic link");
} else {
setSent(true);
}
}
if (sent) {
return (
<div>
<h1>Check your email</h1>
<p>
We sent a sign-in link to <strong>{email}</strong>.
Click the link in the email to sign in.
</p>
<p style={{ color: "#666" }}>
The link expires in 10 minutes. Check your spam folder
if you don't see it.
</p>
<button onClick={() => setSent(false)}>
Try a different email
</button>
</div>
);
}
return (
<form onSubmit={handleSubmit}>
<h1>Sign in with Magic Link</h1>
<input
type="email"
placeholder="your@email.com"
value={email}
onChange={(e) => setEmail(e.target.value)}
required
/>
{error && <p style={{ color: "red" }}>{error}</p>}
<button type="submit" disabled={loading}>
{loading ? "Sending..." : "Send Magic Link"}
</button>
</form>
);
}How It Works
When a user requests a magic link, here is what happens behind the scenes:
- User enters their email — Your app calls
authClient.signIn.magicLink({ email }). - Server generates a token — Banata creates a cryptographically signed token that is valid for 10 minutes.
- Email is sent — Banata uses your configured email provider and the
magic-linktemplate to deliver the sign-in link. - User clicks the link — The browser navigates to the verification endpoint with the token.
- Server verifies the token — Banata checks the token signature and expiration.
- Session is created — A session cookie is set and the user is redirected to your
callbackURL.
New Users vs. Existing Users
Magic links handle both cases automatically:
- New email — If no account exists for that email, Banata creates a new user account. There is no separate sign-up step required.
- Existing email — If the email belongs to an existing user (whether they originally signed up with magic link, email/password, or social OAuth), they are signed into that existing account.
This makes magic links a great option for a unified sign-in/sign-up experience.
Token Lifetime
Magic link tokens expire after 10 minutes. After that, the link becomes invalid and the user needs to request a new one. This short window keeps the authentication flow secure while giving users enough time to check their inbox.
Combining with Other Methods
Magic links work well alongside other authentication methods. A common pattern is to offer magic links as the primary sign-in method with email/password as a fallback:
export default function SignInPage() {
const [mode, setMode] = useState<"magic" | "password">("magic");
return (
<div>
<div>
<button onClick={() => setMode("magic")}>Magic Link</button>
<button onClick={() => setMode("password")}>Password</button>
</div>
{mode === "magic" ? (
<MagicLinkSignIn />
) : (
<PasswordSignIn />
)}
</div>
);
}You can enable both methods in your dashboard under Authentication > Methods, or via the SDK:
await banata.configuration.saveDashboardConfig({
authMethods: {
magicLink: true,
emailPassword: true,
},
});Rate Limiting
Magic link requests are rate-limited to prevent abuse:
| Endpoint | Limit |
|---|---|
| Send magic link | 30 requests per minute (per IP) |
If a user exceeds the rate limit, a RateLimitError (HTTP 429) is returned. You should handle this in your UI and let the user know to wait before trying again.
Security Considerations
- Cryptographically signed — Each magic link token is signed so it cannot be forged or tampered with.
- Single-use — Once a magic link has been used to sign in, the token is invalidated and cannot be reused.
- 10-minute expiry — The short token lifetime limits the window of exposure if a link is intercepted.
- Email account security — Magic links are only as secure as the user's email account. If someone has access to the inbox, they can sign in. For higher-security applications, consider combining magic links with multi-factor authentication for a passwordless + second-factor experience.
Troubleshooting
"Magic link expired"
The token is only valid for 10 minutes. Ask the user to request a new one. Common causes:
- The user took too long to check their email.
- The email was delayed by the provider.
- The user clicked an old link from a previous request.
"Invalid magic link"
The token signature could not be verified. This can happen if:
- The link URL was modified or truncated (some email clients break long URLs).
- The
BETTER_AUTH_SECRETenvironment variable was changed after the link was generated.
Email not received
If the magic link email is not arriving:
- Check that you have an active email provider configured in Emails > Providers.
- Verify that Magic Auth is enabled under Emails > Configuration.
- Make sure the
magic-linktemplate exists in Email Templates. - Ask the user to check their spam or junk folder.
- Confirm the email address is valid and correctly spelled.
Next Steps
- Email & Password — Add traditional sign-in as a fallback
- Social OAuth — Let users sign in with Google, GitHub, and more
- Multi-Factor Auth — Add TOTP as a second factor for extra security
- Organizations — Manage teams and roles for your users