I ran into this problem and didn’t find a clear solution in the docs—so I’m writing about it.
When using Magic Links with AuthJs for authentication, you might want to collect additional user details—like first name
and last name
—during signup. With magic links you don’t get the typical OAuth flow where you can modify user data inside a callback.
The solution? Save user details in the database before authentication.
The Problem
By default, when a user signs in with a magic link, AuthJs only knows their email. If you need extra details like firstName
and lastName
, there’s no built-in way to capture them before the magic link is sent.
Callbacks Won’t Work
AuthJs callbacks like signIn
don’t run before the user receives their magic link, so we can’t modify or store user data there. Instead, we need a pre-authentication step that:
- Saves user details in the database
- Triggers the magic link authentication afterward
The Solution: Server Actions for Signup
1. Create a Server Action to Save User Data
Define a server action to:
- Check if the user exists in the database.
- Insert them if they don’t exist.
- Trigger authentication via magic link.
"use server";
import { db } from "@/lib/db";
import { users } from "@/lib/db/schema";
import { signIn } from "@/auth";
export async function signUp(email: string, firstName: string, lastName: string) {
if (!email || !firstName || !lastName) {
throw new Error("Missing required fields");
}
// Check if user already exists
const existingUser = await db.select().from(users).where(users.email.eq(email));
if (!existingUser.length) {
await db.insert(users).values({ email, firstName, lastName });
}
// Now trigger the magic link authentication
await signIn("resend", { email, redirect: true });
}
2. Use This in a Client-Side Signup Form
The client component calls the server action when the form is submitted.
"use client";
import { useState, useTransition } from "react";
import { signUp } from "@/actions/auth";
export default function SignUpForm() {
const [email, setEmail] = useState("");
const [firstName, setFirstName] = useState("");
const [lastName, setLastName] = useState("");
const [pending, startTransition] = useTransition();
const handleSignUp = (e: React.FormEvent) => {
e.preventDefault();
startTransition(async () => {
try {
await signUp(email, firstName, lastName);
} catch (error) {
alert(error.message);
}
});
};
return (
<form>
</form>
);
}
3. Modify the AuthJs Session Callback
After authentication, we need to retrieve firstName
and lastName
from the database and add them to the session.
import { AuthOptions } from "next-auth";
import { DrizzleAdapter } from "@auth/drizzle-adapter";
import EmailProvider from "next-auth/providers/resend";
import { db } from "@/lib/db";
import { users } from "@/lib/db/schema";
export const authOptions: AuthOptions = {
adapter: DrizzleAdapter(db),
providers: [
EmailProvider({
server: process.env.RESEND_API_KEY!,
from: "[email protected]",
}),
],
callbacks: {
async session({ session, user }) {
const dbUser = await db.select().from(users).where(users.email.eq(user.email));
if (dbUser.length) {
session.user.firstName = dbUser[0].firstName;
session.user.lastName = dbUser[0].lastName;
}
return session;
},
},
};
This ensures:
✔ User details are saved before authentication
✔ Authjs gets enriched session data
✔ Everything works smoothly with Authjs magic links
If you’re using magic links and Authjs, this is the cleanest way to capture and persist additional user details. Hope this helps!