ในบทช่วยสอนนี้ คุณจะได้เรียนรู้การนำการรับรองตัวตนผู้ใช้ไปใช้ในแอปพลิเคชัน Next.js โดยใช้ Auth.js และ Upstash Redis คุณจะได้เรียนรู้วิธีตั้งค่าสภาพแวดล้อมที่จำเป็น สร้าง custom authentication provider สำหรับการลงชื่อเข้าใช้และสมัครสมาชิกอย่างปลอดภัย และผสานรวม Upstash Redis เป็นฐานข้อมูลสำหรับจัดการ sessions
ข้อกำหนดเบื้องต้น
คุณจะต้องมีสิ่งต่อไปนี้:
- Node.js 18 หรือใหม่กว่า
- บัญชี Upstash
Tech Stack
ตั้งค่า Upstash Redis Instance
ใน Upstash dashboard ไปที่แท็บ Redis และสร้างฐานข้อมูล

เลื่อนลงจนกว่าจะเจอส่วน REST API และเลือกปุ่ม .env คัดลอกเนื้อหาและเก็บไว้ที่ปลอดภัย

สร้างแอปพลิเคชัน Next.js ใหม่
เริ่มต้นด้วยการสร้างโปรเจกต์ Next.js ใหม่ เปิดเทอร์มินัลและรันคำสั่งต่อไปนี้:
npx create-next-app@latest my-app
เมื่อถูกพรอมต์ ให้เลือก:
Yesเมื่อถูกถามว่าจะใช้ TypeScript หรือไม่Noเมื่อถูกถามว่าจะใช้ ESLint หรือไม่Yesเมื่อถูกถามว่าจะใช้ Tailwind CSS หรือไม่Noเมื่อถูกถามว่าจะใช้ไดเรกทอรีsrc/หรือไม่Yesเมื่อถูกถามว่าจะใช้ App Router หรือไม่Noเมื่อถูกถามว่าจะใช้ Turbopack หรือไม่Noเมื่อถูกถามว่าจะปรับแต่ง import alias เริ่มต้น (@/*) หรือไม่
เมื่อเสร็จแล้ว ให้เข้าไปในไดเรกทอรีโปรเจกต์และเริ่มแอปในโหมดพัฒนาด้วยคำสั่ง:
cd my-app npm run dev
แอปควรทำงานบน localhost:3000 หยุด development server เพื่อติดตั้ง dependencies ที่จำเป็นด้วยคำสั่งต่อไปนี้:
npm install @upstash/redis nanoid npm install next-auth @auth/core @auth/upstash-redis-adapter
ไลบรารีที่ติดตั้งรวมถึง:
nanoid: ไลบรารีสร้าง ID ที่ไม่ซ้ำและปลอดภัยnext-auth: โซลูชันการรับรองตัวตนสำหรับ Next.js@auth/core: แพ็กเกจหลักสำหรับจัดการการรับรองตัวตนใน Auth.js@upstash/redis: SDK สำหรับโต้ตอบกับ Upstash Redis ผ่าน HTTP requests@auth/upstash-redis-adapter: Adapter สำหรับผสานรวม Auth.js กับ Upstash Redis
ตอนนี้ สร้างไฟล์ .env ที่รากของโปรเจกต์ คุณจะใช้ค่า AUTH_SECRET, UPSTASH_REDIS_REST_URL และ UPSTASH_REDIS_REST_TOKEN
ไฟล์ .env ควรมีเนื้อหาดังนี้:
# .env # Auth.js Environment Variable AUTH_SECRET="..." # Upstash Redis Environment Variables UPSTASH_REDIS_REST_URL="https://...upstash.io" UPSTASH_REDIS_REST_TOKEN="..."
สร้าง Upstash Redis Client Instance
สร้างไฟล์ชื่อ redis.ts ในไดเรกทอรี lib ด้วยโค้ดต่อไปนี้:
// File: lib/redis.ts import { Redis } from '@upstash/redis' export default new Redis({ url: process.env.UPSTASH_REDIS_REST_URL, token: process.env.UPSTASH_REDIS_REST_TOKEN, })
ในโค้ดด้านบน Redis client instance ที่เข้ากันได้กับ Edge ถูกส่งออกเพื่อใช้งานทั่วทั้งแอปพลิเคชัน
ตั้งค่า User Types และ Credentials Helpers
สร้างไฟล์ชื่อ types.ts ในไดเรกทอรี lib ด้วยโค้ดต่อไปนี้:
// File: lib/types.ts export interface UserType { id?: string email: string name?: string image?: string password?: string emailVerified?: string }
Interface UserType กำหนดโครงสร้างของออบเจ็กต์ผู้ใช้ในแอปพลิเคชัน รวมถึง:
id(optional): ตัวระบุเฉพาะของผู้ใช้email(required): อีเมลของผู้ใช้name(optional): ชื่อของผู้ใช้image(optional): URL หรือพาธไปยังรูปโปรไฟล์ของผู้ใช้password(optional): รหัสผ่านที่แฮชของผู้ใช้emailVerified(optional): timestamp หรือ string ที่บ่งชี้ว่าอีเมลของผู้ใช้ได้รับการยืนยันเมื่อไหร่
Interface นี้ทำหน้าที่เป็นการกำหนดประเภทสำหรับข้อมูลผู้ใช้ทั่วทั้งแอปพลิเคชัน
เพิ่มเติม สร้างไฟล์ชื่อ credentials.ts ในไดเรกทอรี lib ด้วยโค้ดต่อไปนี้:
// File: lib/credentials.ts export function generateRandomToken() { const array = new Uint8Array(20) crypto.getRandomValues(array) return Array.from(array, (byte) => byte.toString(16).padStart(2, '0')).join('') } export function generateRandomString(inputValue: string) { const encoder = new TextEncoder() const data = encoder.encode(inputValue) return crypto.subtle.digest('SHA-256', data).then((hashBuffer) => { return Array.from(new Uint8Array(hashBuffer)) .map((byte) => byte.toString(16).padStart(2, '0')) .join('') }) } export async function hashPassword(password: string) { const encoder = new TextEncoder() const data = encoder.encode(password) const hashBuffer = await crypto.subtle.digest('SHA-256', data) return Array.from(new Uint8Array(hashBuffer)) .map((byte) => byte.toString(16).padStart(2, '0')) .join('') } export async function comparePassword(password: string, hash: string) { const hashedPassword = await hashPassword(password) return hashedPassword === hash }
ในโค้ดด้านบน ฟังก์ชันยูทิลิตี้ที่เข้ากันได้กับ Edge สำหรับสร้างและจัดการโทเค็นสุ่มและรหัสผ่านที่แฮชถูกกำหนด:
generateRandomToken: สร้างโทเค็นสุ่มที่ปลอดภัยทางทฤษฎีการเข้ารหัสโดยการสร้างอาร์เรย์สุ่ม 20 ไบต์และแปลงเป็นสตริงฐานสิบหกgenerateRandomString: รับสตริงอินพุท เข้ารหัสเป็นไบต์ แฮชด้วย SHA-256 และคืนค่าแฮชเป็นสตริงฐานสิบหก เหมาะสำหรับสร้างสตริงที่ไม่ซ้ำและสอดคล้องกันจากค่าอินพุทhashPassword: แฮชรหัสผ่านแบบ plaintext ด้วย SHA-256 และแปลงแฮชที่ได้เป็นสตริงฐานสิบหก ใช้สำหรับจัดเก็บรหัสผ่านอย่างปลอดภัยcomparePassword: เปรียบเทียบรหัสผ่านแบบ plaintext กับรหัสผ่านที่แฮชโดยการแฮชอินพุทและตรวจสอบว่าตรงกับแฮชที่จัดเก็บหรือไม่
การใช้ Upstash Redis Adapter กับ Auth.js
สร้างไฟล์ชื่อ auth.ts ในไดเรกทอรี lib ด้วยโค้ดต่อไปนี้:
// File: lib/auth.ts import { UpstashRedisAdapter } from '@auth/upstash-redis-adapter' import NextAuth from 'next-auth' import providers from './providers' import redis from './redis' export const { handlers, signIn, signOut, auth } = NextAuth(() => ({ providers, session: { strategy: 'jwt' }, adapter: UpstashRedisAdapter(redis), }))
ในโค้ดด้านบน การรับรองตัวตนถูกตั้งค่าผ่านการสร้างฟังก์ชัน handlers, signIn, signOut, และ auth โดยใช้การกำหนดค่าที่ปรับแต่งได้ ใช้กลยุทธ์ session แบบ JWT-based, UpstashRedisAdapter เพื่อเชื่อมต่อกับฐานข้อมูล Upstash Redis สำหรับจัดเก็บข้อมูลผู้ใช้ และอาร์เรย์ของ authentication providers ที่กำหนดในโมดูล providers การตั้งค่านี้ช่วยให้การผสานรวมการรับรองตัวตนและการจัดการ session กับ Upstash Redis ในฐานะ backend ทำงานได้ราบรื่น
ตั้งค่า Custom Authentication Provider ด้วย Auth.js
สร้างไฟล์ชื่อ providers.ts ในไดเรกทอรี lib ด้วยโค้ดต่อไปนี้:
// File: lib/providers.ts import { nanoid } from 'nanoid' import Credentials from 'next-auth/providers/credentials' import { comparePassword, generateRandomString, hashPassword } from './credentials' import redis from './redis' import { UserType } from './types' export default [ Credentials({ credentials: { email: {}, password: {}, }, authorize: async (credentials, request) => { let type try { const tmp = new URL(request.url).searchParams.get('kind') if (tmp && typeof tmp === 'string') type = tmp } catch (e) {} if (!type || !credentials.email || typeof credentials.email !== 'string' || !credentials.password || typeof credentials.password !== 'string') return null const randomizedPassword = await generateRandomString(credentials.password) const userByEmail = await redis.get<string | null | undefined>(`user:email:${credentials.email}`) if (userByEmail) { if (type !== 'in') { console.log(`can not sign in in a non sign-in mode.`) throw new Error(`can not sign in in a non sign-in mode.`) } const user = await redis.get<UserType>(`user:${userByEmail}`) if (!user) { console.log(`Found the user by email from user:email:${userByEmail}, but the user object is missing at user:${userByEmail}`) return null } if (user.password) { const hashedPassword = await hashPassword(randomizedPassword) const isPasswordCorrect = await comparePassword(user.password, hashedPassword) if (isPasswordCorrect) { const { password, ...rest } = user return rest } throw new Error(`incorrect password for credentials.`) } throw new Error(`you are using some other authentication method already, but not credentials.`) } else { if (type !== 'up') { console.log(`can not sign up in a non sign-up mode.`) throw new Error(`can not sign up in a non sign-up mode.`) } const newUser = { name: null, image: null, emailVerified: null, email: credentials.email, password: randomizedPassword, } const tmp = nanoid() await redis.set(`user:email:${credentials.email}`, tmp) await redis.set(`user:${tmp}`, newUser) return newUser } }, }), ]
ในโค้ดด้านบน อาร์เรย์ของ custom credentials-based authentication provider ถูกส่งออก Credentials provider จัดการทั้ง logic การลงชื่อเข้าใช้และสมัครสมาชิก: ตรวจสอบว่าผู้ใช้มีอยู่ใน Redis หรือไม่ และ إما ยืนยันรหัสผ่านของพวกเขา หรือ สร้างผู้ใช้ใหม่ หากลงชื่อเข้าใช้ มันเปรียบเทียบรหัสผ่านที่ให้มา (แฮชโดยใช้สตริงสุ่ม) กับรหัสผ่านที่จัดเก็บ หากสมัครสมาชิก มันสร้างรายการผู้ใช้ใหม่ใน Upstash Redis โค้ดใช้ Upstash Redis สำหรับจัดเก็บข้อมูลผู้ใช้ และรับรองว่ากระบวนการทำงานในโหมด sign-up หรือ sign-in ตามพารามิเตอร์ kind ใน URL
ตั้งค่า Authentication API Routes (powered by Auth.js)
สร้างไฟล์ชื่อ route.ts ในไดเรกทอรี app/api/auth/[...nextauth] ด้วยโค้ดต่อไปนี้:
// File: app/api/auth/[...nextauth]/route.ts export const runtime = 'edge' import { handlers } from '@/lib/auth' export const { GET, POST } = handlers
ในโค้ดด้านบน ตัวจัดการ endpoint สองตัว GET และ POST ถูกส่งออกซึ่งจัดการโดยฟังก์ชัน handlers ที่ส่งออกจากไฟล์ @/lib/auth.ts
สร้างไฟล์ชื่อ route.ts ในไดเรกทอรี app/api/refresh ด้วยโค้ดต่อไปนี้:
// File: app/api/refresh/route.ts export const runtime = 'edge' export const dynamic = 'force-dynamic' export const fetchCache = 'force-no-store' import { auth } from '@/lib/auth' import redis from '@/lib/redis' import { UserType } from '@/lib/types' import { encode } from '@auth/core/jwt' import { cookies } from 'next/headers' import { NextResponse } from 'next/server' export async function GET(request: Request) { const useSecureCookie = request.url.startsWith('https:') const salt = useSecureCookie ? '__Secure-authjs.session-token' : 'authjs.session-token' if (!process.env.AUTH_SECRET) return new NextResponse(null, { status: 500 }) const [session, cookieStore] = await Promise.all([auth(), cookies()]) if (!session?.user?.email) return new NextResponse(null, { status: 400 }) const userByEmail = await redis.get(`user:email:${session.user.email}`) const userData = await redis.get<UserType>(`user:${userByEmail}`) if (!userData?.email) cookieStore.set(salt, toString(), { secure: useSecureCookie, path: '/', httpOnly: true, sameSite: 'lax', maxAge: 0 }) else { const { image, password, ...rest } = userData const saltVal = await encode({ salt, secret: process.env.AUTH_SECRET, token: { ...rest, picture: image } }) cookieStore.set(salt, saltVal, { secure: useSecureCookie, path: '/', httpOnly: true, sameSite: 'lax' }) } return new NextResponse() }
ในโค้ดด้านบน API route ที่เข้ากันได้กับ Edge สำหรับรีเฟรช session ของผู้ใช้ถูกสร้าง มันดึงข้อมูล session ของผู้ใช้และข้อมูลที่เกี่ยวข้องจาก Redis ยืนยันอีเมลของผู้ใช้ และอัปเดตคุกกี้ session หากข้อมูลผู้ใช้ไม่ถูกต้องหรือหายไป คุกกี้ session จะถูกล้าง มิฉะนั้น โทเค็นที่ลงนามใหม่จะถูกสร้างและจัดเก็บในคุกกี้
สร้างไฟล์ชื่อ route.ts ในไดเรกทอรี app/api/user ด้วยโค้ดต่อไปนี้:
// File: app/api/user/route.ts export const runtime = 'edge' export const dynamic = 'force-dynamic' export const fetchCache = 'force-no-store' import { auth } from '@/lib/auth' import redis from '@/lib/redis' import { UserType } from '@/lib/types' import { NextResponse } from 'next/server' export async function POST(request: Request) { try { const session = await auth() if (!session?.user?.email) return new NextResponse(null, { status: 400 }) const body = await request.json() const userByEmail = await redis.get(`user:email:${session.user.email}`) const userData = await redis.get<UserType>(`user:${userByEmail}`) if (!userData) return new NextResponse(null, { status: 404 }) if (body.name) userData.name = body.name if (body.image) userData.image = body.image await redis.set(`user:${userByEmail}`, userData) return new NextResponse() } catch (e: any) { const message = e.message || e.toString() console.log(message) return new NextResponse(message, { status: 500 }) } }
ในโค้ดด้านบน API route ที่เข้ากันได้กับ Edge สำหรับอัปเดตข้อมูลผู้ใช้ถูกสร้าง มันรับรองตัวตนผู้ใช้ ดึงข้อมูลของพวกเขาจาก Redis โดยใช้อีเมล และอัปเดตข้อมูลของพวกเขา (เช่น ชื่อหรือรูปภาพ) ตาม request body หากไม่พบผู้ใช้หรือเกิดข้อผิดพลาด รหัสสถานะและการตอบกลับที่เหมาะสมจะถูกคืนค่า
ตั้งค่า Next.js Middleware ด้วย Auth.js
สร้างไฟล์ชื่อ middleware.ts ในไดเรกทอรีรากของโปรเจกต์ด้วยโค้ดต่อไปนี้:
// File: middleware.ts export { auth as middleware } from '@/lib/auth' export const config = { matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)'], }
ในโค้ดด้านบน middleware ถูกสร้างโดยการ re-export ฟังก์ชัน auth จากโมดูล @/lib/auth Middleware ใช้การรับรองตัวตนกับทุก routes ยกเว้น API routes, ไฟล์สแตติก, image assets, และ favicon ตามที่ระบุโดยการกำหนดค่า matcher นี่รับรองว่า routes ที่ไม่ได้ถูกยกเว้นจะได้รับการปกป้องโดยตรรกะการรับรองตัวตนที่ตั้งค่าไว้กับ Auth.js
ใช้ Auth.js ใน Next.js App Router Components
สร้างไฟล์ชื่อ NextAuthProvider.tsx ในไดเรกทอรี app ด้วยโค้ดต่อไปนี้:
// File: app/NextAuthProvider.tsx 'use client' import { SessionProvider } from 'next-auth/react' type Props = { children?: React.ReactNode } export default function ({ children }: Props) { return <SessionProvider>{children}</SessionProvider> }
ในโค้ดด้านบน React component ด้าน client ถูกสร้างเพื่อห่อหุ้มแอปพลิเคชันด้วย SessionProvider ของ NextAuth Component ยอมรับ children เป็น props และรับรองว่าคอมโพเนนต์ที่ถูกห่อหุ้มมีการเข้าถึงบริบท session ของผู้ใช้ที่ NextAuth จัดเตรียม การตั้งค่านี้จำเป็นสำหรับการจัดการสถานะการรับรองตัวตนทั่วทั้งแอปพลิเคชันใน Next.js
สร้างไฟล์ชื่อ csrf.tsx ในไดเรกทอรี app ด้วยโค้ดต่อไปนี้:
// File: app/csrf.tsx 'use client' import { getCsrfToken } from 'next-auth/react' import { useEffect, useState } from 'react' export function CSRFInput() { const [csrfToken, setCsrfToken] = useState<string>() useEffect(() => { getCsrfToken().then((res) => setCsrfToken(res)) }, []) return <input type="hidden" name="csrfToken" defaultValue={csrfToken} /> }
ในโค้ดด้านบน React component ด้าน client ถูกส่งออกที่ดึง CSRF token โดยใช้ฟังก์ชัน getCsrfToken ของ NextAuth และจัดเก็บในตัวแปร state Component ทำการเรนเดอร์ input field แบบ hidden ที่มี CSRF token อนุญาตให้ส่งฟอร์มอย่างปลอดภัยโดยรวมโทเค็นเป็นค่าที่ซ่อนอยู่ นี่มีประโยชน์สำหรับการป้องกันการโจมตี cross-site request forgery
สร้างไฟล์ชื่อ provider.tsx ในไดเรกทอรี app ด้วยโค้ดต่อไปนี้:
// File: app/provider.tsx 'use client' import { Button } from '@/components/ui/button' import { Icon } from '@iconify/react' import { signIn } from 'next-auth/react' export default function ({ provider, prefix }: { prefix: string; provider: { name: string; id: string } }) { return ( <Button onClick={() => signIn(provider.id)} key={provider.name} variant="outline" className="flex w-full gap-x-3"> {provider.id === 'google' && <Icon fontSize={18} icon="flat-color-icons:google" />} <span className="text-black"> {prefix} with {provider.name} </span> </Button> ) }
ในโค้ดด้านบน React component สำหรับเรนเดอร์ปุ่มลงชื่อเข้าใช้ที่ปรับแต่งได้สำหรับ authentication providers ต่างๆ ถูกส่งออก มันยอมรับ provider (ประกอบด้วย name และ id) และ prefix เป็น props ปุ่มทริกเกอร์ฟังก์ชัน signIn สำหรับ provider ที่ระบุ และแสดงไอคอนและฉลากแบบไดนามิกตาม id ของ provider
อัปเดตไฟล์ชื่อ layout.tsx ในไดเรกทอรี app ด้วยโค้ดต่อไปนี้:
// File: app/layout.tsx import './globals.css' import NextAuthProvider from './NextAuthProvider' export default function ({ children, }: Readonly<{ children: React.ReactNode }>) { return ( <html lang="en"> <body className="bg-white text-black antialiased"> <NextAuthProvider> <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 flex flex-col">{children}</div> </NextAuthProvider> </body> </html> ) }
ในโค้ดด้านบน Next.js layout กำลังห่อหุ้มหน้าทั้งหมดด้วย global styles และ authentication context มันนำเข้า globals.css สำหรับการจัดสไตล์ทั่วไป และ NextAuthProvider เพื่อจัดการสถานะการรับรองตัวตน children prop แทนเนื้อหาหน้าที่ซ้อนกัน ซึ่งถูกห่อหุ้มในคอนเทนเนอร์ที่มีการจัดสไตล์พร้อม padding ที่ตอบสนองและความกว้างสูงสุด ให้เลย์เอาต์ที่สอดคล้องสำหรับทุกหน้า
โหลดสถานะการรับรองตัวตนของผู้ใช้ในหน้า client-side
อัปเดตไฟล์ชื่อ page.tsx ในไดเรกทอรี app ด้วยโค้ดต่อไปนี้:
// File: app/page.tsx 'use client' import { signOut, useSession } from 'next-auth/react' export default function () { const { data, status } = useSession() return ( <> {JSON.stringify({ data, status })} <button onClick={() => signOut()}>Sign Out</button> </> ) }
ในโค้ดด้านบน React component ด้าน client ที่แสดงข้อมูล session ของผู้ใช้และให้ปุ่ม "Sign Out" ถูกส่งออก มันใช้ hook useSession ของ NextAuth เพื่อดึงข้อมูล session และสถานะ แสดงพวกมันเป็น JSON string ฟังก์ชัน signOut ถูกเรียกเมื่อปุ่มถูกคลิก อนุญาตให้ผู้ใช้ล็อกเอาท์
สร้างหน้า Sign-in แบบไดนามิก (ใช้ Auth.js)
สร้างไฟล์ชื่อ page.tsx ในไดเรกทอรี app/signin ด้วยโค้ดต่อไปนี้:
// File: app/signin/page.tsx export const runtime = 'edge' import { CSRFInput } from '@/app/csrf' import Provider from '@/app/provider' import { Button } from '@/components/ui/button' import { Input } from '@/components/ui/input' import { Label } from '@/components/ui/label' import { auth } from '@/lib/auth' import providers from '@/lib/providers' import { unstable_noStore } from 'next/cache' import Link from 'next/link' import { redirect } from 'next/navigation' export default async function () { unstable_noStore() const session = await auth() if (session) redirect('/') return ( <div className="border md:border-white/10 flex items-center justify-center py-12"> <div className="mx-auto grid w-[350px] gap-6"> <div className="grid gap-2 text-center"> <h1 className="text-3xl font-bold">Sign In</h1> <p className="text-balance text-muted-foreground">Enter your email below to sign in to your account</p> </div> {providers .filter((provider) => (typeof provider === 'function' ? provider({}).id : provider.id) !== 'credentials') .map((provider) => ( <Provider prefix="Sign in" key={typeof provider === 'function' ? provider({}).name : provider.name} provider={typeof provider === 'function' ? provider({}) : provider} /> ))} <p className="text-gray-300 text-xs text-center">OR</p> <form method="POST" className="grid gap-4" action="/api/auth/callback/credentials?kind=in"> <div className="grid gap-2"> <Label htmlFor="email">Email</Label> <Input name="email" id="email" type="email" placeholder="m@example.com" required /> </div> <div className="grid gap-2"> <Label htmlFor="password">Password</Label> <Input name="password" id="password" type="password" required /> <CSRFInput /> </div> <Button type="submit" className="w-full"> Sign In </Button> </form> <div className="mt-4 text-center text-sm"> Don't have an account?{' '} <Link href="/signup" className="underline"> Sign up </Link> </div> </div> </div> ) }
ในโค้ดด้านบน หน้า sign-in กำลังถูกสร้าง มันตรวจสอบว่ามี user session อยู่แล้วหรือไม่ หากมี จะ redirect ไปที่ homepage หน้าแสดงฟอร์มลงชื่อเข้าใช้ที่ผู้ใช้สามารถป้อนอีเมลและรหัสผ่าน พร้อมตัวเลือกในการลงชื่อเข้าใช้ผ่าน providers ต่างๆ มันยังรวม CSRF token เพื่อความปลอดภัยและลิงก์ไปหน้าสมัครสมาชิกสำหรับผู้ใช้ใหม่
สร้างหน้า Sign-up แบบไดนามิก (ใช้ Auth.js)
สร้างไฟล์ชื่อ page.tsx ในไดเรกทอรี app/signup ด้วยโค้ดต่อไปนี้:
// File: app/signup/page.tsx export const runtime = 'edge' import { CSRFInput } from '@/app/csrf' import Provider from '@/app/provider' import { Button } from '@/components/ui/button' import { Input } from '@/components/ui/input' import { Label } from '@/components/ui/label' import { auth } from '@/lib/auth' import providers from '@/lib/providers' import { unstable_noStore } from 'next/cache' import Link from 'next/link' import { redirect } from 'next/navigation' export default async function () { unstable_noStore() const session = await auth() if (session) redirect('/') return ( <div className="border md:border-white/10 flex items-center justify-center py-12"> <div className="mx-auto grid w-[350px] gap-6"> <div className="grid gap-2 text-center"> <h1 className="text-3xl font-bold">Sign Up</h1> <p className="text-balance text-muted-foreground">Enter your email below to sign up with an account</p> </div> {providers .filter((provider) => (typeof provider === 'function' ? provider({}).id : provider.id) !== 'credentials') .map((provider) => ( <Provider prefix="Sign up" key={typeof provider === 'function' ? provider({}).name : provider.name} provider={typeof provider === 'function' ? provider({}) : provider} /> ))} <p className="text-gray-300 text-xs text-center">OR</p> <form method="POST" className="grid gap-4" action="/api/auth/callback/credentials?kind=up"> <div className="grid gap-2"> <Label htmlFor="email">Email</Label> <Input name="email" id="email" type="email" placeholder="m@example.com" required /> </div> <div className="grid gap-2"> <div className="flex items-center"> <Label htmlFor="password">Password</Label> </div> <Input name="password" id="password" type="password" required /> <CSRFInput /> </div> <Button type="submit" className="w-full"> Sign Up </Button> </form> <div className="mt-4 text-center text-sm"> Don't have an account?{' '} <Link href="/signin" className="underline"> Sign In </Link> </div> </div> </div> ) }
ในโค้ดด้านบน หน้า sign-up กำลังถูกสร้าง มันตรวจสอบว่ามี user session อยู่แล้วหรือไม่ หากมี จะ redirect ไปที่ homepage หน้าแสดงฟอร์มสมัครสมาชิกที่ผู้ใช้สามารถป้อนอีเมลและรหัสผ่าน พร้อมตัวเลือกในการลงชื่อเข้าใช้ผ่าน providers ต่างๆ มันยังรวม CSRF token เพื่อความปลอดภัยและลิงก์ไปหน้าลงชื่อเข้าใช้สำหรับผู้ใช้ที่มีอยู่แล้ว
นั่นคือการเรียนรู้เยอะมาก! คุณเสร็จสิ้นแล้ว ✨
อ้างอิง
สำหรับข้อมูลเชิงลึกเพิ่มเติม สำรวจอ้างอิงที่อ้างอิงในบล็อกนี้
- GitHub Repository
- Auth.js - Credentials Authentication
- Auth.js - Upstash Redis Adapter
- Next.js - Cookies
สรุป
ในบทช่วยสอนนี้ คุณได้เรียนรู้วิธีนำการรับรองตัวตนผู้ใช้ไปใช้ในแอปพลิเคชัน Next.js โดยใช้ Auth.js และ Upstash Redis คุณได้เรียนรู้การตั้งค่าตัวแปรสภาพแวดล้อม การสร้าง Redis client และการนำ custom authentication provider ไปใช้ นอกจากนี้ คุณยังได้เรียนรู้วิธีตั้งค่า API routes ใน Next.js สำหรับจัดการกระบวนการ sign-in และ sign-up ของผู้ใช้ รวมถึงการจัดการ session ด้วยการทำตามขั้นตอนเหล่านี้ คุณสามารถจัดการการรับรองตัวตนและจัดเก็บข้อมูลผู้ใช้ในแอปพลิเคชัน Next.js ของคุณได้อย่างมีประสิทธิภาพด้วย Upstash Redis