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

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

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

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

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

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

กลุ่มเทคโนโลยี

เทคโนโลยี คำอธิบาย Next.jsReact Framework สำหรับแพลตฟอร์มฐานข้อมูล Web.UpstashServerless คุณจะใช้ Upstash Redis เพื่อจัดการเซสชันและข้อมูลผู้ใช้

ตั้งค่าอินสแตนซ์ Upstash Redis

ในแดชบอร์ด Upstash ของคุณ ให้ไปที่ Redis แท็บและสร้างฐานข้อมูล

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

เลื่อนลงไปจนกว่าคุณจะพบส่วน REST API และเลือก 01 ปุ่ม คัดลอกเนื้อหาและบันทึกไว้ในที่ที่ปลอดภัย

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

สร้างแอปพลิเคชัน Next.js ใหม่

เริ่มต้นด้วยการสร้างโครงการ Next.js ใหม่ เปิดเทอร์มินัลของคุณและรันคำสั่งต่อไปนี้:

npx create-next-app@latest my-app

เมื่อได้รับแจ้ง ให้เลือก:

  • 10 เมื่อได้รับแจ้งให้ใช้ TypeScript
  • 29 เมื่อได้รับแจ้งให้ใช้ ESLint
  • 32 เมื่อได้รับแจ้งให้ใช้ Tailwind CSS
  • 42 เมื่อได้รับแจ้งให้ใช้ 56 ไดเร็กทอรี
  • 60 เมื่อได้รับแจ้งให้ใช้ App Router
  • 75 เมื่อได้รับแจ้งให้ใช้ Turbopack
  • 86 เมื่อได้รับแจ้งให้ปรับแต่งนามแฝงการนำเข้าเริ่มต้น (96 ).

เมื่อเสร็จแล้ว ให้ย้ายไปยังไดเร็กทอรีโปรเจ็กต์และเริ่มแอปในโหมดการพัฒนาโดยดำเนินการคำสั่งต่อไปนี้:

cd my-app
npm run dev

แอปควรทำงานบน localhost:3000 หยุดเซิร์ฟเวอร์การพัฒนาเพื่อติดตั้งการขึ้นต่อกันที่จำเป็นด้วยคำสั่งต่อไปนี้:

npm install @upstash/redis nanoid
npm install next-auth @auth/core @auth/upstash-redis-adapter

ไลบรารีที่ติดตั้งได้แก่:

  • 108 :ไลบรารีสำหรับสร้าง ID ที่ปลอดภัยและไม่ซ้ำใคร
  • 118 :โซลูชันการตรวจสอบสิทธิ์ที่สร้างขึ้นสำหรับ Next.js
  • 121 :แพ็คเกจหลักสำหรับการจัดการการรับรองความถูกต้องใน Auth.js
  • 135 :SDK เพื่อโต้ตอบกับ Upstash Redis ผ่านคำขอ HTTP
  • 146 :อะแดปเตอร์สำหรับการรวม Auth.js กับ Upstash Redis

ตอนนี้ สร้าง 157 ไฟล์ที่รากของโครงการของคุณ คุณจะใช้ 169 , 178 และ 184 ค่า

197 ไฟล์ควรมีดังต่อไปนี้:

# .env
 
# Auth.js Environment Variable
AUTH_SECRET="..."
 
# Upstash Redis Environment Variables
UPSTASH_REDIS_REST_URL="https://...upstash.io"
UPSTASH_REDIS_REST_TOKEN="..."

สร้างอินสแตนซ์ไคลเอนต์ Upstash Redis

สร้างไฟล์ชื่อ 201 ใน 215 ไดเร็กทอรีด้วยรหัสต่อไปนี้:

// 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 ที่เข้ากันได้กับ Edge จะถูกส่งออกเพื่อใช้ทั่วทั้งแอปพลิเคชัน

ตั้งค่าประเภทผู้ใช้และตัวช่วยข้อมูลประจำตัว

สร้างไฟล์ชื่อ 228 ใน 237 ไดเร็กทอรีด้วยรหัสต่อไปนี้:

// File: lib/types.ts
 
export interface UserType {
 id?: string
 email: string
 name?: string
 image?: string
 password?: string
 emailVerified?: string
}

245 ส่วนต่อประสานกำหนดโครงสร้างของวัตถุผู้ใช้ในแอปพลิเคชัน ประกอบด้วย:

  • 251 (ไม่บังคับ):ตัวระบุเฉพาะสำหรับผู้ใช้
  • 263 (จำเป็น):ที่อยู่อีเมลของผู้ใช้
  • 273 (ไม่บังคับ):ชื่อผู้ใช้
  • 283 (ไม่บังคับ):URL หรือเส้นทางไปยังรูปโปรไฟล์ของผู้ใช้
  • 294 (ไม่บังคับ):รหัสผ่านที่แฮชของผู้ใช้
  • 301 (ไม่บังคับ):การประทับเวลาหรือสตริงที่ระบุว่าอีเมลของผู้ใช้ได้รับการยืนยันหรือไม่

อินเทอร์เฟซนี้ทำหน้าที่เป็นคำจำกัดความประเภทสำหรับข้อมูลผู้ใช้ทั่วทั้งแอปพลิเคชัน

นอกจากนี้ ให้สร้างไฟล์ชื่อ 319 ใน 321 ไดเร็กทอรีด้วยรหัสต่อไปนี้:

// 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 สำหรับการสร้างและจัดการโทเค็นที่ปลอดภัยและรหัสผ่านที่แฮช:

  • 337 :สร้างโทเค็นแบบสุ่มที่ปลอดภัยด้วยการเข้ารหัสโดยสร้างอาร์เรย์สุ่มขนาด 20 ไบต์แล้วแปลงเป็นสตริงเลขฐานสิบหก
  • 349 :ยอมรับสตริงอินพุต เข้ารหัสเป็นไบต์ แฮชโดยใช้ SHA-256 และส่งกลับแฮชเป็นสตริงเลขฐานสิบหก มีประโยชน์สำหรับการสร้างสตริงที่ไม่ซ้ำใครที่สอดคล้องกันซึ่งได้มาจากค่าอินพุต
  • 359 :แฮชรหัสผ่านข้อความธรรมดาโดยใช้ SHA-256 และแปลงแฮชผลลัพธ์เป็นสตริงเลขฐานสิบหก ใช้สำหรับจัดเก็บรหัสผ่านอย่างปลอดภัย
  • 362 :เปรียบเทียบรหัสผ่านข้อความธรรมดากับรหัสผ่านที่แฮชโดยการแฮชอินพุตและตรวจสอบว่าตรงกับแฮชที่เก็บไว้หรือไม่

การใช้อะแดปเตอร์ Upstash Redis กับ Auth.js

สร้างไฟล์ชื่อ 373 ใน 383 ไดเร็กทอรีด้วยรหัสต่อไปนี้:

// 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),
}))

ในโค้ดข้างต้น กำลังตั้งค่าการตรวจสอบสิทธิ์ผ่านการสร้าง 392 , 407 , 419 และ 428 ฟังก์ชั่นที่ใช้การกำหนดค่าแบบกำหนดเอง ใช้กลยุทธ์เซสชันที่ใช้ JWT 435 เพื่อเชื่อมต่อกับฐานข้อมูล Upstash Redis เพื่อจัดเก็บข้อมูลผู้ใช้ และอาร์เรย์ของผู้ให้บริการการตรวจสอบสิทธิ์ที่กำหนดใน 441 โมดูล การตั้งค่านี้ช่วยให้สามารถผสานรวมการตรวจสอบสิทธิ์และการจัดการเซสชันกับ Upstash Redis เป็นแบ็กเอนด์ได้อย่างราบรื่น

ตั้งค่าผู้ให้บริการการตรวจสอบสิทธิ์แบบกำหนดเองด้วย Auth.js

สร้างไฟล์ชื่อ 452 ใน 466 ไดเร็กทอรีด้วยรหัสต่อไปนี้:

// 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
 }
 },
 }),
]

ในโค้ดด้านบน อาร์เรย์ของผู้ให้บริการการตรวจสอบสิทธิ์แบบกำหนดเองตามข้อมูลประจำตัวจะถูกส่งออก ผู้ให้บริการข้อมูลรับรองจะจัดการทั้งตรรกะในการลงชื่อเข้าใช้และการลงทะเบียน โดยจะตรวจสอบว่ามีผู้ใช้อยู่ใน Redis หรือไม่ และตรวจสอบรหัสผ่านหรือสร้างผู้ใช้ใหม่ หากลงชื่อเข้าใช้ ระบบจะเปรียบเทียบรหัสผ่านที่ให้มา (แฮชโดยใช้สตริงสุ่ม) กับรหัสผ่านที่เก็บไว้ หากลงทะเบียน ระบบจะสร้างรายการผู้ใช้ใหม่ใน Upstash Redis รหัสใช้ Upstash Redis สำหรับการจัดเก็บข้อมูลผู้ใช้ และตรวจสอบให้แน่ใจว่ากระบวนการเสร็จสิ้นในโหมดลงชื่อสมัครใช้หรือลงชื่อเข้าใช้ตามพารามิเตอร์ประเภทใน URL

ตั้งค่าเส้นทาง API การตรวจสอบสิทธิ์ (สนับสนุนโดย Auth.js)

สร้างไฟล์ชื่อ 471 ใน 482 ไดเร็กทอรีด้วยรหัสต่อไปนี้:

// File: app/api/auth/[...nextauth]/route.ts
 
export const runtime = 'edge'
 
import { handlers } from '@/lib/auth'
 
export const { GET, POST } = handlers

ในโค้ดด้านบน ตัวจัดการปลายทางสองตัว 495 และ 507 ถูกส่งออกที่ได้รับการจัดการโดย 511 ฟังก์ชั่นส่งออกจาก 524 ไฟล์.

สร้างไฟล์ชื่อ 531 ใน 548 ไดเร็กทอรีด้วยรหัสต่อไปนี้:

// 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 ที่เข้ากันได้กับ Edge สำหรับการรีเฟรชเซสชันของผู้ใช้กำลังถูกสร้างขึ้น โดยดึงเซสชันผู้ใช้และข้อมูลที่เกี่ยวข้องจาก Redis ยืนยันอีเมลของผู้ใช้ และอัปเดตคุกกี้เซสชัน หากข้อมูลผู้ใช้ไม่ถูกต้องหรือหายไป คุกกี้เซสชันจะถูกล้าง มิฉะนั้น โทเค็นที่ลงนามใหม่จะถูกสร้างขึ้นและจัดเก็บไว้ในคุกกี้

สร้างไฟล์ชื่อ 554 ใน 568 ไดเร็กทอรีด้วยรหัสต่อไปนี้:

// 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 ที่เข้ากันได้กับ Edge สำหรับการอัปเดตข้อมูลผู้ใช้ โดยจะตรวจสอบสิทธิ์ผู้ใช้ ดึงข้อมูลจาก Redis โดยใช้อีเมล และอัปเดตข้อมูล (เช่น ชื่อหรือรูปภาพ) ตามเนื้อหาคำขอ หากไม่พบผู้ใช้หรือมีข้อผิดพลาดเกิดขึ้น รหัสสถานะและการตอบกลับที่เหมาะสมจะถูกส่งกลับ

ตั้งค่ามิดเดิลแวร์ Next.js ด้วย Auth.js

สร้างไฟล์ชื่อ 578 ในไดเร็กทอรีรากของโปรเจ็กต์ของคุณด้วยโค้ดต่อไปนี้:

// File: middleware.ts
 
export { auth as middleware } from '@/lib/auth'
 
export const config = {
 matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)'],
}

ในโค้ดด้านบน มิดเดิลแวร์จะถูกสร้างขึ้นโดยการส่งออก 583 อีกครั้ง ฟังก์ชั่นจาก 590 โมดูล มิดเดิลแวร์ใช้การรับรองความถูกต้องกับทุกเส้นทาง ยกเว้นเส้นทาง API, ไฟล์คงที่, เนื้อหารูปภาพ และไอคอน Fav ตามที่ระบุโดย 603 การกำหนดค่า เพื่อให้แน่ใจว่าเส้นทางที่ไม่ถูกแยกได้รับการปกป้องโดยตรรกะการตรวจสอบสิทธิ์ที่ตั้งค่าด้วย Auth.js

ใช้ Auth.js ในส่วนประกอบเราเตอร์ของแอป Next.js

สร้างไฟล์ชื่อ 610 ใน 621 ไดเร็กทอรีด้วยรหัสต่อไปนี้:

// 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 ฝั่งไคลเอ็นต์จะถูกสร้างขึ้นเพื่อล้อมแอปพลิเคชันด้วย 636 ของ NextAuth . ส่วนประกอบยอมรับ 641 เป็นอุปกรณ์ประกอบฉากและตรวจสอบให้แน่ใจว่าส่วนประกอบที่ห่อไว้นั้นสามารถเข้าถึงบริบทเซสชันผู้ใช้ที่ NextAuth มอบให้ การตั้งค่านี้จำเป็นสำหรับการจัดการสถานะการตรวจสอบสิทธิ์ทั่วทั้งแอปพลิเคชันใน Next.js

สร้างไฟล์ชื่อ 653 ใน 662 ไดเร็กทอรีด้วยรหัสต่อไปนี้:

// 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 ฝั่งไคลเอ็นต์จะถูกส่งออกซึ่งดึงโทเค็น CSRF โดยใช้ 676 ของ NextAuth ฟังก์ชั่นและเก็บไว้ในตัวแปรสถานะ ส่วนประกอบจะแสดงฟิลด์อินพุตที่ซ่อนอยู่ซึ่งมีโทเค็น CSRF ช่วยให้ส่งแบบฟอร์มได้อย่างปลอดภัยโดยรวมโทเค็นเป็นค่าที่ซ่อนอยู่ สิ่งนี้มีประโยชน์ในการป้องกันการโจมตีการปลอมแปลงคำขอข้ามไซต์

สร้างไฟล์ชื่อ 685 ใน 699 ไดเร็กทอรีด้วยรหัสต่อไปนี้:

// 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 สำหรับการแสดงผลปุ่มลงชื่อเข้าใช้ที่ปรับแต่งได้สำหรับผู้ให้บริการการตรวจสอบสิทธิ์ต่างๆ จะถูกส่งออก ยอมรับ 704 (ประกอบด้วย 718 และ 727 ) และ 736 เป็นอุปกรณ์ประกอบฉาก ปุ่มนี้จะทริกเกอร์ 748 ฟังก์ชั่นสำหรับผู้ให้บริการที่ระบุและแสดงไอคอนและป้ายกำกับแบบไดนามิกตาม 752 ของผู้ให้บริการ .

อัปเดตไฟล์ชื่อ 764 ใน 779 ไดเร็กทอรีด้วยรหัสต่อไปนี้:

// 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 กำลังล้อมทั้งหน้าในรูปแบบสากลและบริบทการตรวจสอบสิทธิ์ มันนำเข้า 786 สำหรับสไตล์สากลและ 798 เพื่อจัดการสถานะการรับรองความถูกต้อง 803 prop แสดงถึงเนื้อหาของหน้าที่ซ้อนกัน ซึ่งถูกห่อไว้ในคอนเทนเนอร์ที่มีสไตล์พร้อมช่องว่างภายในแบบตอบสนองและความกว้างสูงสุด ทำให้มีเค้าโครงที่สอดคล้องกันสำหรับทุกหน้า

โหลดสถานะการตรวจสอบสิทธิ์ผู้ใช้ในหน้าฝั่งไคลเอ็นต์

อัปเดตไฟล์ชื่อ 812 ใน 827 ไดเร็กทอรีด้วยรหัสต่อไปนี้:

// 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 ฝั่งไคลเอ็นต์ที่แสดงข้อมูลเซสชันของผู้ใช้และมีปุ่ม "ออกจากระบบ" จะถูกส่งออก ใช้ 838 ของ NextAuth hook เพื่อดึงข้อมูลและสถานะเซสชันโดยแสดงเป็นสตริง JSON 844 ฟังก์ชันจะถูกเรียกเมื่อมีการคลิกปุ่ม เพื่อให้ผู้ใช้สามารถออกจากระบบได้

สร้างหน้าลงชื่อเข้าใช้แบบไดนามิก (โดยใช้ Auth.js)

สร้างไฟล์ชื่อ 858 ใน 869 ไดเร็กทอรีด้วยรหัสต่อไปนี้:

// 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>
 )
}

ในโค้ดด้านบน หน้าลงชื่อเข้าใช้กำลังถูกสร้างขึ้น ตรวจสอบว่ามีเซสชันผู้ใช้อยู่แล้วหรือไม่ หากเป็นเช่นนั้น ระบบจะเปลี่ยนเส้นทางไปยังหน้าแรก หน้านี้จะแสดงแบบฟอร์มลงชื่อเข้าใช้ที่ผู้ใช้สามารถป้อนอีเมลและรหัสผ่านของตน พร้อมด้วยตัวเลือกในการลงชื่อเข้าใช้โดยใช้ผู้ให้บริการต่างๆ นอกจากนี้ยังมีโทเค็น CSRF เพื่อความปลอดภัยและลิงก์ไปยังหน้าลงทะเบียนสำหรับผู้ใช้ใหม่

สร้างหน้าลงทะเบียนแบบไดนามิก (โดยใช้ Auth.js)

สร้างไฟล์ชื่อ 870 ใน 888 ไดเร็กทอรีด้วยรหัสต่อไปนี้:

// 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>
 )
}

ในโค้ดด้านบน หน้าลงทะเบียนกำลังถูกสร้างขึ้น ตรวจสอบว่ามีเซสชันผู้ใช้อยู่แล้วหรือไม่ หากเป็นเช่นนั้น ระบบจะเปลี่ยนเส้นทางไปยังหน้าแรก หน้านี้จะแสดงแบบฟอร์มลงทะเบียนที่ผู้ใช้สามารถป้อนอีเมลและรหัสผ่านของตน พร้อมด้วยตัวเลือกในการลงชื่อเข้าใช้โดยใช้ผู้ให้บริการต่างๆ นอกจากนี้ยังมีโทเค็น CSRF เพื่อความปลอดภัยและลิงก์ไปยังหน้าลงชื่อเข้าใช้สำหรับผู้ใช้ปัจจุบัน

นั่นเป็นการเรียนรู้มากมาย! คุณทำเสร็จแล้วตอนนี้ ✨

ข้อมูลอ้างอิง

สำหรับข้อมูลเชิงลึกโดยละเอียดเพิ่มเติม โปรดสำรวจข้อมูลอ้างอิงที่อ้างถึงในบล็อกนี้

  • พื้นที่เก็บข้อมูล GitHub
  • Auth.js - การตรวจสอบข้อมูลรับรอง
  • Auth.js - อะแดปเตอร์ Upstash Redis
  • Next.js - คุกกี้

บทสรุป

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