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

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

สร้างแอปพลิเคชัน Next.js ใหม่
เริ่มต้นด้วยการสร้างโครงการ Next.js ใหม่ เปิดเทอร์มินัลของคุณและรันคำสั่งต่อไปนี้:
npx create-next-app@latest my-app เมื่อได้รับแจ้ง ให้เลือก:
10รหัส> เมื่อได้รับแจ้งให้ใช้ TypeScript29รหัส> เมื่อได้รับแจ้งให้ใช้ ESLint32รหัส> เมื่อได้รับแจ้งให้ใช้ Tailwind CSS42รหัส> เมื่อได้รับแจ้งให้ใช้56ไดเร็กทอรี60รหัส> เมื่อได้รับแจ้งให้ใช้ App Router75รหัส> เมื่อได้รับแจ้งให้ใช้ Turbopack86รหัส> เมื่อได้รับแจ้งให้ปรับแต่งนามแฝงการนำเข้าเริ่มต้น (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.js121รหัส> :แพ็คเกจหลักสำหรับการจัดการการรับรองความถูกต้องใน Auth.js135รหัส> :SDK เพื่อโต้ตอบกับ Upstash Redis ผ่านคำขอ HTTP146รหัส> :อะแดปเตอร์สำหรับการรวม 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'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'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 ได้อย่างมีประสิทธิภาพ