boychawin.com
เข้าสู่ระบบด้วย Credentials Provider In NextAuth.js Prisma EP.3

เข้าสู่ระบบด้วย Credentials Provider In NextAuth.js Prisma EP.3

เข้าสู่ระบบโดยใช้ชื่อผู้ใช้และรหัสผ่าน ได้ง่ายๆ ด้วย provider credentials.

Boy Chawin
Boy Chawin

Dec 6, 2023

เดียววันนี้ผมจะมาพา ตั้งค่า NextAuth.js(v4) ซึ่งก็ประกอบด้วยขั้นตอนไม่กี่ขั้นตอน ตั้งแต่การติดตั้งไปจนถึงการกำหนดค่า(Config) มาเริ่มกันเลย (จะพาใช้ Tool ที่ Basic ที่สุดให้ทุกคนเข้าใจง่ายนะครับ

แนะนำก่อนเริ่ม

ขั้นตอนที่ 1: การติดตั้ง

ติดตั้ง NextAuth.js และแพ็คเกจที่จำเป็น ก็คือการใช้คำสั่ง npm install เพื่อติดตั้ง next-auth และ @next-auth/prisma-adapter ในโปรเจกต์ของคุณ โดยคำสั่งที่คุณจะใช้คือ

# ใช้ npm 
npm install next-auth @next-auth/prisma-adapter @types/bcrypt  zod

ขั้นตอนที่ 2: การกำหนดค่า

สร้างไฟล์ [...nextauth]/route.ts ในไดเร็กทอรี src/app/api/auth

// route.ts
import { authOptions } from "@/utils/auth";
import NextAuth from "next-auth";

const handler = NextAuth(authOptions);

export { handler as GET, handler as POST };

และ สร้าง Function แยกชื่อ authOptions จะเก็บไว้ที่ src/utils/prisma.ts

// utils/prisma.ts
import { getServerSession, type NextAuthOptions } from "next-auth";
import { PrismaAdapter } from "@next-auth/prisma-adapter";
import prisma from "./prisma";
import CredentialsProvider from "next-auth/providers/credentials";
import { compare } from 'bcrypt'
import { z } from 'zod';

const loginUserSchema = z.object({
  username: z.string().regex(/^[a-z0-9_-]{3,15}$/g, 'Invalid username'),
  password: z.string().min(5, 'Password should be minimum 5 characters'),
});


export const authOptions: NextAuthOptions = {
  providers: [
    CredentialsProvider({
      type: "credentials",
      name: 'Credential',
      credentials: {
        username: { type: 'text', placeholder: 'test@test.com' },
        password: { type: 'password', placeholder: 'Password' },
      },
      async authorize(credentials) {

        try {
          const { username, password } = loginUserSchema.parse(credentials);
          const user = await prisma.user.findFirst({
            where: { username },
            select: { id: true, name: true, email: true, password: true, role: true, username: true }
          });
          if (!user) return null;

          const isPasswordValid = await compare(password, user.password);

          if (!isPasswordValid) return null;

          return { ...user, id: String(user.id), };
        } catch (error: unknown) {
          return null
        }


      },
    })
  ],
  secret: process.env.NEXT_PUBLIC_SECRET,
  pages: {
    signIn: '/',
    signOut: '/login',
    error: '/error',
    verifyRequest: '/login',
    newUser: '/'
  },
  adapter: PrismaAdapter(prisma),
  session: {
    strategy: 'jwt',
    maxAge: 30 * 24 * 60 * 60, // 30 days
    updateAge: 24 * 60 * 60, // 24 hours
  },

  callbacks: {
    session({ session, token }) {
      session.user = {
        ...session.user,
        // @ts-expect-error
        id: token.sub!,
        // @ts-expect-error
        role: token.user.role!,
        // @ts-expect-error
        username: token.user.username!
      };
      return session;
    },
    jwt({ token, user }) {
      if (user) {
        token.user = user;
      }
      return token;
    },
  },
};

export function getSession() {
  return getServerSession(authOptions) as Promise<{
    user: {
      id: string;
      name: string;
      username: string;
      email: string;
      role: string;
    };
  } | null>;
}

คำอธิบายโค้ดด้านบน

การตั้งค่าระบบการยืนยันตัวตน (authentication) โดยใช้ NextAuth ร่วมกับ Prisma สำหรับการจัดเก็บข้อมูลและการตรวจสอบข้อมูลผู้ใช้งานผ่านชื่อผู้ใช้และรหัสผ่าน (username/password) โดยมีขั้นตอนดังนี้:

  1. การ Import และการกำหนดค่า: Import modules ที่จำเป็นจาก NextAuth, Prisma, และอื่นๆ รวมถึงการกำหนด schema สำหรับข้อมูลการเข้าสู่ระบบด้วย Zod.

  2. Authentication Options (authOptions):

    • Providers: ใช้ CredentialsProvider จาก NextAuth เพื่อดำเนินการยืนยันตัวตนโดยใช้ชื่อผู้ใช้และรหัสผ่าน.

    • authorize(credentials): ฟังก์ชัน async ที่พยายามยืนยันตัวตนผู้ใช้โดยการตรวจสอบข้อมูลของผู้ใช้กับฐานข้อมูล Prisma

      • แปลงข้อมูลของผู้ใช้ที่ส่งมาใช้ loginUserSchema.
      • ค้นหาผู้ใช้ในฐานข้อมูล Prisma โดยใช้ชื่อผู้ใช้ที่ให้มา
      • ถ้าพบผู้ใช้ จะทำการเปรียบเทียบรหัสผ่านที่ให้มากับรหัสผ่านที่ถูกเข้ารหัสอยู่ในฐานข้อมูลโดยใช้ bcrypt.compare.
      • หากรหัสผ่านตรงกัน จะคืนข้อมูลของผู้ใช้พร้อมกับบางฟิลด์ที่เลือกไว้พร้อมกับ ID ที่แปลงเป็น string
      • หากมีข้อผิดพลาดใดๆ ที่เกิดขึ้นระหว่างกระบวนการนี้ จะคืนค่า null
    • Secret: ใช้ environment variable (NEXT_PUBLIC_SECRET) เพื่อกำหนดค่า Secret สำหรับการเข้ารหัส session.

    • Pages: กำหนดเส้นทางที่แตกต่างกันสำหรับการกระทำที่เกี่ยวข้องกับการยืนยันตัวตน เช่น เข้าสู่ระบบ (sign in), ออกจากระบบ (sign out), การจัดการข้อผิดพลาด (error), การยืนยันการร้องขอ (verify request), และการลงทะเบียนผู้ใช้ใหม่ (new user registration).

    • Adapter: ใช้ PrismaAdapter เพื่อเชื่อมต่อ NextAuth กับ Prisma ORM เพื่อจัดการข้อมูล session storage.

    • Session Options: กำหนดค่าเกี่ยวกับวิธีการ session, อายุสูงสุด, และการอัพเดท session.

    • Callbacks: จัดการการสร้างและปรับแต่ง session และ JWT (JSON Web Tokens).

      • Callback session ปรับเพิ่มหรืออัพเดทข้อมูลที่เกี่ยวข้องกับผู้ใช้ใน session object.
      • Callback jwt รับ user object และแนบมันกับ JWT token หากมีผู้ใช้.

โดยรวมโค้ดนี้ทำการตั้งค่าระบบยืนยันตัวตนด้วย NextAuth ร่วมกับ Prisma เพื่อจัดการข้อมูลฐานข้อมูลและการยืนยันตัวตนของผู้ใช้ โดยใช้ข้อมูลการเข้าสู่ระบบแบบ Credential-based และจัดการ session และ token สำหรับการยืนยันตัวตนของผู้ใช้

ขั้นตอนที่ 3: เพิ่มตัวแปรสภาพแวดล้อม

สร้างไฟล์ .env.development ในไดเร็กทอรีรากและเพิ่มข้อมูลรับรองตัวตนของคุณ

# Config NextAuth.js
NEXTAUTH_URL=http://localhost:3000
NEXT_PUBLIC_SECRET="secret"
NEXT_PUBLIC_ROOT_DOMAIN='localhost:3000'

ขั้นตอนที่ 4: ใช้ NextAuth.js ในหน้า Next.js ของคุณ

ดำเนินการตรวจสอบสิทธิ์ในหน้าของคุณ

คุณสามารถใช้ useSession hook หรือ getSession function ที่ NextAuth.js จัดเตรียมเพื่อเข้าถึง session ของผู้ใช้ได้ แต่เรามี function ที่เราสร้างไว้อยู่แล้วชื่อ getSession

// app/page.tsx
import LoginForm from "@/components/LoginForm";
import Profile from "@/components/Profile";
import { getSession } from "@/utils/auth";

export default async function Page() {


  const session = await getSession();

  if (session?.user) {
     return <Profile session={session} />
  }

  return (
    <>
      <div className=" justify-center items-center text-center my-8">
        <LoginForm />
        <p className="mt-2">
          Not registered yet? <a className=" hover:underline text-sm" href='/register'>Register here</a>
        </p>
      </div>
    </>
  )
}

และ สร้าง component ชื่อ LoginForm และ Profile ไว้ที่ src/components/..

// LoginForm.tsx
"use client"
import { signIn } from "next-auth/react";
import { FormButton } from "./FormButton";
import { redirect } from "next/navigation";
import { useState } from "react";

export default function LoginForm() {
  const [errorMessage, setError] = useState<string | null>(null)

  const handleSubmit = async (formData: FormData) => {
    const username = formData.get("username") as string;
    const password = formData.get("password") as string;

    const { error, status, ok, url }: any = await signIn('credentials', {
      username: username,
      password: password,
      redirect: false,
      callbackUrl: '/',
    });

    if (error) {
      setError("Username or Password is incorrect")
      console.error(error)
    }

    if (status == 200 && ok) {
      /* if callbackUrl != / use redirect(url) */
      window.location.reload()
    }


  }
  return (
    <form action={handleSubmit} className="grid gap-4" >
      <div>
        <label htmlFor="username" className="mr-2">Username</label>
        <input
          className="border"
          type="text"
          id="username"
          name="username"
          placeholder=""
          required
        />
      </div>
      <div>
        <label htmlFor="password" className="mr-2">Password</label>
        <input
          className="border "
          type="password"
          id="password"
          name="password"
          required
        />
      </div>
      <div>
        {!!errorMessage && <p className="text-red-500">{errorMessage}</p>}
      </div>
      <div>
        <FormButton>Login</FormButton>
      </div>
    </form>
  );
}
// Profile.tsx
"use client"
import { signOut } from "next-auth/react";

export default function Profile({ session }: {
  session: any;
}) {
  return (
    <div className=" justify-center items-center text-center my-8">

      <h2>Hello: {session.user.name}</h2>
      <p>UserID: {session.user.id}</p>
      <p>
        Data session:  {JSON.stringify(session.user)}
      </p>
      <p>

        <button onClick={() => signOut()} className="py-1 px-6 border mt-5 bg-red-500 text-white"> Logout</button>
      </p>
    </div>
  );
}

ขั้นตอนที่ 5: เริ่มต้นแอปพลิเคชัน Next.js ของคุณ

รันแอปพลิเคชัน Next.js ของคุณ

# ใช้ npm
npm run dev

# ใช้ Yarn
yarn dev

หมายเหตุ:

  • เรามีโค้ดให้ทั้งหมด ดาวน์โหลดได้ที่ปุ่มด้านบน
  • เดียวจะมี Login with Google, Facebook, GitHub ตามมาครับ
  • อย่าลืมอ่านเอกสารของ NextAuth.js เพื่อเรียนรู้เพิ่มเติม

ระบบจองห้อง/โต๊ะ PHP & MySQLi

ระบบจองห้อง, ระบบจัดการทรัพยากรสารสนเทศ

ระบบจองห้อง/โต๊ะ PHP & MySQLi

Nov 22, 2023

Web ระบบเบิก-จ่ายวัสดุ

ระบบเบิก-จ่ายวัสดุ อุปกรณ์และเครื่องเขียนสำนักงาน API PHP/MySQL

Web ระบบเบิก-จ่ายวัสดุ

Dec 5, 2023

How to install LINE (LIFF) ร่วมกับ React

การใช้ LINE Front-end Framework (LIFF) ร่วมกับ React และ TypeScript สำหรับการรับรองตัวตนและการเข้าถึงข้อมูล

How to install LINE (LIFF) ร่วมกับ React

Dec 6, 2023

How to install Prisma in Next.js 14

การติดตั้งและ setup โปรเจกต์ Next.js เพื่อใช้งานกับ Prisma อาจมีขั้นตอนหลายอย่างที่ต้องทำเพื่อให้โปรเจกต์สามารถเชื่อมต่อกับฐานข้อมูลและใช้ Prisma ได้อย่างถูกต้อง

How to install Prisma in Next.js 14

Dec 7, 2023

How to install Next.js 14 แบบมีรายละเอียด

พา setup โปรเจกต์ Next.js 14 เบื้องต้น

How to install Next.js 14  แบบมีรายละเอียด

Dec 7, 2023

[Free] Next.js Admin Dashboard Template with Tailwind CSS

Tailwind CSS-Powered Next.js Admin Dashboard Template

[Free] Next.js Admin Dashboard Template with Tailwind CSS

Dec 11, 2023

How to use CRUD with Prisma in Next.js

การใช้งาน CRUD (Create, Read, Update, Delete) กับ Prisma ใน Next.js

How to use CRUD with Prisma in Next.js

Dec 12, 2023

How to Install and using MAMP

ขั้นตอนในการติดตั้งและใช้งาน MAMP (Mac,Windows, Apache, MySQL, PHP)

How to Install and using MAMP

Dec 12, 2023

ระบบร้านอาหาร RESTFul API in Golang

Restaurant System API

ระบบร้านอาหาร  RESTFul API in Golang

Dec 18, 2023

สร้างตารางข้อมูล Excel โดย PhpSpreadsheet ด้วย PHP

สร้างตารางข้อมูลใน Excel โดยใช้ไลบรารี PhpSpreadsheet ซึ่งเป็นไลบรารีสำหรับจัดการไฟล์ Excel ด้วย PHP

สร้างตารางข้อมูล Excel โดย PhpSpreadsheet ด้วย PHP

Dec 21, 2023