Banata

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.


Via the Dashboard

The quickest way to turn on magic links is through your Banata dashboard:

  1. Go to Authentication > Methods.
  2. Toggle Magic Link to enabled.
  3. 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).
  4. Optionally, customize the magic-link template under Email Templates.

Via the SDK

You can also enable magic links programmatically:

typescript
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:

typescript
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

ParameterTypeRequiredDescription
emailstringYesThe user's email address
callbackURLstringNoWhere 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:

tsx
"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:

  1. User enters their email — Your app calls authClient.signIn.magicLink({ email }).
  2. Server generates a token — Banata creates a cryptographically signed token that is valid for 10 minutes.
  3. Email is sent — Banata uses your configured email provider and the magic-link template to deliver the sign-in link.
  4. User clicks the link — The browser navigates to the verification endpoint with the token.
  5. Server verifies the token — Banata checks the token signature and expiration.
  6. 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:

tsx
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:

typescript
await banata.configuration.saveDashboardConfig({
  authMethods: {
    magicLink: true,
    emailPassword: true,
  },
});

Rate Limiting

Magic link requests are rate-limited to prevent abuse:

EndpointLimit
Send magic link30 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

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.

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_SECRET environment variable was changed after the link was generated.

Email not received

If the magic link email is not arriving:

  1. Check that you have an active email provider configured in Emails > Providers.
  2. Verify that Magic Auth is enabled under Emails > Configuration.
  3. Make sure the magic-link template exists in Email Templates.
  4. Ask the user to check their spam or junk folder.
  5. Confirm the email address is valid and correctly spelled.

Next Steps