nextauthjs / nextauthjs/next-auth
Unable to Trigger Custom Sign In Flow after User Creation via NextAuth V4
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 28.4k
- Forks
- 4k
- PR merge metrics
- No merged PRs in 30d
Description
Provider type
Environment
System:
OS: macOS 14.1.2
CPU: (12) arm64 Apple M2 Max
Memory: 2.07 GB / 32.00 GB
Shell: 5.9 - /bin/zsh
Binaries:
Node: 20.8.0 - ~/.nvm/versions/node/v20.8.0/bin/node
npm: 10.1.0 - ~/.nvm/versions/node/v20.8.0/bin/npm
pnpm: 8.6.12 - /opt/homebrew/bin/pnpm
bun: 1.0.7 - ~/.bun/bin/bun
Browsers:
Chrome: 120.0.6099.109
Chrome Canary: 122.0.6188.0
Edge: 120.0.2210.77
Safari: 17.1.2
npmPackages:
@auth/drizzle-adapter: ^0.3.6 => 0.3.6
next: 14.0.3 => 14.0.3
next-auth: ^4.24.5 => 4.24.5
react: 18.2.0 => 18.2.0
Reproduction URL
https://github.com/juancamiloqhz/next-auth-signup-issue
Describe the issue
Hello everyone,
I am currently developing an application utilizing create-t3-app along with NextAuth and Drizzle. I am employing a database session strategy and an email provider for passwordless authentication.
However, I have encountered a unique issue when attempting to implement a custom sign-up flow. In this process, users are required to sign up with their name, username, and email, which is managed via a custom API route at /api/signup.
This sequence proceeds as expected, where I validate the email and username for uniqueness in my database before account creation. The problem arises after the user is registered, wherein I attempt to spawn the NextAuth endpoint at /api/auth/signin/email. Unfortunately, I am unable to trigger the Sign In flow, and as a consequence, the user is not sent an email with the login link.
Here you can find the flow initiation within my react component:
async function onSubmit(data: FormData) {
setIsLoading(true)
setFormErrorMessage(null)
setFormSuccessMessage({ title: null, message: null })
try {
const signInResult = await fetch("/api/signup", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
email: data.email.toLowerCase(),
name: data.name,
role: "user",
username: data.username,
callbackUrl: searchParams?.get("from") ?? "/dashboard",
}),
})
const json = (await signInResult.json()) as {
ok: boolean
error: string | null
url: string
status: number
}
if (!json.ok && json.error) {
return setFormErrorMessage(json.error)
}
setFormSuccessMessage({
title: "¡Account created!",
message:
"We sent you an email with the login link. Please check the spam folder.",
})
} catch (error: unknown) {
console.error("error: ", error)
if (error instanceof Error) {
setFormErrorMessage(
"An error has occurred, please try again later."
)
}
} finally {
setIsLoading(false)
}
}
And here is the custom signup endpoint at /api/signup:
import { NextResponse } from "next/server"
import { db } from "@/server/db"
import { UserRole, users } from "@/server/db/schema"
import { getCsrfToken } from "next-auth/react"
import { z } from "zod"
import { env } from "@/env"
type SignUpRole = Exclude<UserRole, UserRole.Admin>
const signupSchema = z.object({
name: z.string(),
email: z.string().email(),
username: z
.string()
.refine(
(username) =>
/^[^\s]*$/.test(username) && /^[a-zA-Z0-9_-]+$/.test(username),
{
message:
"Username cannot contain spaces or special characters (except underscore, hyphen)",
}
),
role: z.nativeEnum(UserRole).refine((role) => role !== UserRole.Admin, {
message: "Role should not be admin",
}),
callbackUrl: z.string(),
})
export async function POST(req: Request) {
try {
const { name, email, username, role, callbackUrl } = (await req.json()) as {
name: string
email: string
username: string
role: SignUpRole
callbackUrl: string
}
// Validate data
const result = await signupSchema.safeParseAsync({
name,
email,
username,
role,
callbackUrl,
})
if (!result.success) {
return new Response("Invalid data.", { status: 400 })
}
// Check if email is already in use
const existingUser = await db.query.users.findFirst({
where: (queryUser, { eq }) => eq(queryUser.email, email.toLowerCase()),
})
if (existingUser) {
return NextResponse.json(
{
error: "This email is already in use.",
status: 409,
ok: false,
url: null,
},
{ status: 409 }
)
}
// Check if username is already in use
const existingUsername = await db.query.users.findFirst({
where: (queryUser, { eq }) => eq(queryUser.username, username),
})
if (existingUsername) {
return NextResponse.json(
{
error: "This username is already in use",
status: 409,
ok: false,
url: null,
},
{ status: 409 }
)
}
// Create user
await db.insert(users).values({
id: crypto.randomUUID(),
name,
email: email.toLowerCase(),
username: username.toLowerCase(),
role,
})
// Call signin API route from next-auth
const res = await fetch(
`${env.NEXT_PUBLIC_APP_URL}/api/auth/signin/email`,
{
method: "post",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
// @ts-expect-error - Is how they have it in next-auth signin method
body: new URLSearchParams({
email,
csrfToken: await getCsrfToken(),
redirect: false,
callbackUrl,
json: true,
}),
}
)
console.log("res: ", res.ok, res.status, res.statusText)
const data = (await res.json()) as { url: string }
const error = new URL(data.url).searchParams.get("error")
return NextResponse.json(
{
error,
status: res.status,
ok: res.ok,
url: error ? null : data.url,
},
{ status: 201 }
)
} catch (error) {
console.log("Error: ", error)
return new Response("Error", { status: 500 })
}
}
When I try to perform a sign-up operation, the response from /api/signup endpoint is:
{
"error": null,
"status": 200,
"ok": true,
"url": "http://localhost:3000/api/auth/signin?csrf=true"
}
This implies that the NextAuth sign-in flow is not being initiated, and the user does not receive the intended sign-in email. Any help in addressing this situation would be greatly appreciated.
How to reproduce
Prerequisites:
Make sure you have the following prerequisites installed:
- Docker Desktop
- Node (v20.8.0)
- pnpm
Steps to Reproduce the Issue:
Follow the step-by-step instructions below to reproduce the issue in the local environment:
-
Begin by cloning the repository to your local machine. https://github.com/juancamiloqhz/next-auth-signup-issue
-
Once that is done, run the command
make dev. This command will install necessary dependencies and initiate a new MySQL database in Docker. -
(Optional Step) Run
make db-seedif you want to seed the database. -
Start the development server by running the
make devcommand. -
Next, navigate to
http://localhost:3000/registerin your browser. -
Attempt to sign up as either a user or a company:
- If signing up as a user, refer to the code under
src/components/signup-user-form.tsx. - If signing up as a company, refer to the code under
src/components/signup-company-form.tsx.
- If signing up as a user, refer to the code under
Both options call the `/api/signup' endpoint which contains the behaviour I described above.
Expected behavior
Expected Behavior:
Upon a successful call to the /api/signup endpoint, we expect the sign-in process to be initiated following the call to /api/auth/signin/email.
According to the configuration in auth.ts (NextAuth options), within the sendVerificationRequest function, a login link should be logged out on the server console.
Current Behavior:
Contrary to expectations, upon calling the /api/signup endpoint, the sign-in flow doesn't proceed as anticipated. Consequently, no login URL is displayed on the server console. As a result, the authentication process isn't completed, preventing users who newly registered from receiving the sign-in email.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with the /api/signup endpoint and the sign-in calls from src/components/signup-user-form.tsx and src/components/signup-company-form.tsx. Compare its request to /api/auth/signin/email and inspect auth.ts, especially sendVerificationRequest, using the linked reproduction repository. Done means a successful signup initiates the email sign-in flow and produces the expected login URL.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- next.js, react, typescript
- Domain
- api, authentication, backend
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100