All articles

How to use Auth5 with Next14

May 09, 2024

Recently, I have embarked on the development of a new project, which involves the implementation of user login functionality. After conducting research, I am planning to adopt a combination of Next.js 14, Auth.js 5, and shadcn UI for user authentication and verification. I will not dive into the details of installing Next.js and Auth.js.

To begin with, I will use shadcn to create a basic login component, which uses the useForm hook from react-hook-form to create a form.

export const signInSchema = z.object({
  username: z
    .string({
      required_error: "Username is required",
    })
    .min(1, {
      message: "Username required!",
    }),
  password: z
    .string({
      required_error: "Username is required",
    })
    .min(1, {
      message: "Password required!",
    }),
});

export function LoginForm() {
  const form = useForm<
    z.infer<typeof signInSchema>
  >({
    resolver: zodResolver(signInSchema),
    defaultValues: {
      username: "",
      password: "",
    },
  });

  function onSubmit(
    values: z.infer<typeof formSchema>
  ) {
    console.log(values);
  }

  return (
    <Form {...form}>
      <form
        onSubmit={form.handleSubmit(onSubmit)}
        className="space-y-6"
      >
        <FormField
          control={form.control}
          name="username"
          render={({ field }) => (
            <FormItem>
              <FormLabel>Username</FormLabel>
              <FormControl>
                <Input
                  placeholder="Input username"
                  {...field}
                />
              </FormControl>
              <FormMessage />
            </FormItem>
          )}
        />
        <FormField
          control={form.control}
          name="password"
          render={({ field }) => (
            <FormItem>
              <FormLabel>Password</FormLabel>
              <FormControl>
                <Input
                  placeholder="Input password"
                  {...field}
                />
              </FormControl>
              <FormMessage />
            </FormItem>
          )}
        />
        <Button type="submit" className="w-full">
          Login
        </Button>
      </form>
    </Form>
  );
}

At now, after obtaining the username and password upon input, the next step is to utilize Auth.js for authentication. I will create an anth folder at the root directory to manage verification and permissions across the entire project.

export const {
  handlers: { GET, POST },
  signIn,
  signOut,
  auth,
} = NextAuth({
  providers: [
    Credentials({
      async authorize({ username, password }) {
        // pass check
        if (checkUser(username, password)) {
          const user: User = {
            name: username,
          };
          return user;
        } else {
          return null;
        }
      },
    }),
  ],
});

Initialise the Credentials provider in the Auth.js configuration file, which exports signIn and signOut function. And then, the onSubmit function needs to be handled, which means that a sign-in request should be initiated after clicking submit.

const onSubmit = async (
  data: z.infer<typeof signInSchema>
) => {
  await signIn("credentials", data);
};

However, the use client directive is used in the top of the file to indicate a client-side component. This will attempt to execute authentication logic on the client side, which Auth.js server-side authentication cannot handle, resulting in the following error:

Error: `headers` was called outside a request scope

In that case, a new file needs to be created using the use server directive to create a signInAction for the login process.

export const signInAction = async (
  _prevState: string | undefined,
  data: z.infer<typeof signInSchema>
) => {
  try {
    await signIn("credentials", data);
  } catch (error) {
    if (
      `${error}`.includes(KEY_CREDENTIALS_SIGN_IN_ERROR) ||
      `${error}`.includes(KEY_CREDENTIALS_SIGN_IN_ERROR_URL)
    ) {
      // Return credentials error to display on sign-in page.
      return KEY_CREDENTIALS_SIGN_IN_ERROR;
    } else if (!`${error}`.includes("NEXT_REDIRECT")) {
      console.log("Unknown sign in error:", {
        errorText: `${error}`,
        error,
      });
      // Rethrow non-redirect errors
      throw error;
    }
  }
  redirect(data[KEY_CALLBACK_URL] || PATH_ADMIN_PHOTOS);
};

Now, clicking the login button can trigger authentication, but the pending field of useFormStatus cannot be used. We also need to extract the click button component and use useFormState hook.

By the way, In React 19, a new hook called useActionState is introduced to replace and improve the useFormState hook from ReactDOM.

antcao.me © 2022-PRESENT

: 0x9aB9C...7ee7d