Computer >> บทช่วยสอนคอมพิวเตอร์ >  >> การเขียนโปรแกรม >> Redis

การรับรองตัวตนผู้ใช้ปลอดภัยใน Next.js ด้วย Auth.js และ Upstash Redis

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

ข้อกำหนดเบื้องต้น

คุณจะต้องมีสิ่งต่อไปนี้:

  • Node.js 18 หรือใหม่กว่า
  • บัญชี Upstash

Tech Stack

TechnologyDescriptionNext.jsThe React Framework for the Web.UpstashServerless database platform. You are going to use Upstash Redis for managing sessions and user information.

ตั้งค่า Upstash Redis Instance

ใน Upstash dashboard ไปที่แท็บ Redis และสร้างฐานข้อมูล

การรับรองตัวตนผู้ใช้ปลอดภัยใน Next.js ด้วย Auth.js และ Upstash Redis

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

การรับรองตัวตนผู้ใช้ปลอดภัยใน Next.js ด้วย Auth.js และ Upstash Redis

สร้างแอปพลิเคชัน 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&apos;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&apos;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