๐Ÿก ์ˆ™๋ฐ• ์˜ˆ์•ฝ ํ”Œ๋žซํผ: NextAuth ์‚ฌ์šฉ์ž ์ธ์ฆ ๊ตฌํ˜„ํ•˜๊ธฐ (4)

0
post-thumbnail

๋ณธ ๊ธ€์€ ํŒจ์ŠคํŠธ์บ ํผ์Šค โ€“ Next.js ์‹ค๋ฌด ๊ฐ•์˜ ์ค‘ Part 8. Next.js 13์œผ๋กœ ์ˆ™๋ฐ• ์˜ˆ์•ฝ ํ”Œ๋žซํผ ๋งŒ๋“ค๊ธฐ๋ฅผ ์ˆ˜๊ฐ•ํ•˜๋ฉฐ ํ•™์Šตํ•œ ๋‚ด์šฉ์„ ์ •๋ฆฌํ•œ ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๐Ÿ™๐Ÿป


๐Ÿ‘‹๐Ÿป NextAuth ์‚ฌ์šฉ์ž ์ธ์ฆ ๊ตฌํ˜„ํ•˜๊ธฐ

โ–ช๏ธ NextAuth๋ž€?

NextAuth.js๋Š” OAuth ๊ธฐ๋ฐ˜์˜ ์ธ์ฆ ํ”Œ๋กœ์šฐ๋ฅผ ์‰ฝ๊ฒŒ ๊ตฌ์„ฑํ•  ์ˆ˜ ์žˆ๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋‹ค. ๊ฐ„๋‹จํ•œ ์„ค์ •๋งŒ์œผ๋กœ๋„ ์†Œ์…œ ๋กœ๊ทธ์ธ, ์„ธ์…˜ ๊ด€๋ฆฌ, ์‚ฌ์šฉ์ž ์ •๋ณด ์ €์žฅ๊นŒ์ง€ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, Next.js์™€์˜ ํ˜ธํ™˜์„ฑ๋„ ๋›ฐ์–ด๋‚˜๋‹ค.

์ฃผ์š” ํŠน์ง•

  • ๊ฐ„ํŽธํ•œ ์„ค์ •: ์ตœ์†Œํ•œ์˜ ์„ค์ •๋งŒ์œผ๋กœ ๋กœ๊ทธ์ธ ๊ธฐ๋Šฅ ๋น ๋ฅด๊ฒŒ ๊ตฌ์„ฑ ๊ฐ€๋Šฅ
  • ๋‹ค์–‘ํ•œ ์ธ์ฆ ์ œ๊ณต์ž: Google, Naver, Kakao ๋“ฑ ์†Œ์…œ ๋กœ๊ทธ์ธ ์ง€์›
  • ๋ณด์•ˆ ๊ธฐ๋Šฅ ๋‚ด์žฅ: ์„ธ์…˜, JWT, CSRF ๋“ฑ ์ž๋™ ์ฒ˜๋ฆฌ
  • DB ์—ฐ๋™ ๊ฐ€๋Šฅ: ์‚ฌ์šฉ์ž ๊ณ„์ •ยท์„ธ์…˜ ์ •๋ณด ์ €์žฅ ๋ฐ ํ™œ์šฉ ๊ฐ€๋Šฅ
  • Next.js ์™„์ „ ํ˜ธํ™˜: CSR๊ณผ SSR ๋ชจ๋‘์—์„œ ์•ˆ์ •์ ์œผ๋กœ ์ž‘๋™

์ฝœ๋ฐฑ(callback) ํ•จ์ˆ˜ ์ œ๊ณต
NextAuth๋Š” ์ธ์ฆ ํ๋ฆ„์„ ์„ธ๋ถ€์ ์œผ๋กœ ์ œ์–ดํ•  ์ˆ˜ ์žˆ๋„๋ก ์ฝœ๋ฐฑ ํ•จ์ˆ˜(callbacks)๋ฅผ ์ œ๊ณตํ•œ๋‹ค. ์ธ์ฆ ๊ฒฐ๊ณผ ์ฒ˜๋ฆฌ, ๋ฆฌ๋””๋ ‰์…˜, ์„ธ์…˜ยทJWT ๋ฐ์ดํ„ฐ ๊ตฌ์„ฑ ๋“ฑ ๋‹ค์–‘ํ•œ ์‹œ์ ์— ๊ฐœ์ž…ํ•  ์ˆ˜ ์žˆ๋‹ค.

callbacks: {
  signIn,     // ๋กœ๊ทธ์ธ ์„ฑ๊ณต ์—ฌ๋ถ€ ๊ฒฐ์ •
  redirect,   // ๋กœ๊ทธ์ธ ํ›„ ๋ฆฌ๋””๋ ‰์…˜ ๊ฒฝ๋กœ ์„ค์ •
  session,    // ์„ธ์…˜์— ์ „๋‹ฌํ•  ๋ฐ์ดํ„ฐ ์ˆ˜์ •
  jwt         // JWT ํ† ํฐ ๋ฐœ๊ธ‰/๊ฐฑ์‹  ์‹œ ๋ฐ์ดํ„ฐ ์กฐ์ž‘
}

JWT vs ์„ธ์…˜ ๊ธฐ๋ฐ˜ ์ธ์ฆ ๋ฐฉ์‹
NextAuth๋Š” ์„ธ์…˜ ๊ธฐ๋ฐ˜๊ณผ JWT ๊ธฐ๋ฐ˜ ์ธ์ฆ ๋ฐฉ์‹์„ ๋ชจ๋‘ ์ง€์›ํ•œ๋‹ค.

๐Ÿ” JWT vs ์„ธ์…˜ ๊ธฐ๋ฐ˜ ์ธ์ฆ ๋ฐฉ์‹ ์ฐจ์ด

  • JWT (JSON Web Token)
  • ํด๋ผ์ด์–ธํŠธ์— ์ €์žฅ๋˜๋Š” ๋ฌด์ƒํƒœ(stateless) ํ† ํฐ
  • ์š”์ฒญ๋งˆ๋‹ค ํ† ํฐ์„ ํ—ค๋”์— ํฌํ•จ์‹œ์ผœ ์‚ฌ์šฉ์ž ์‹๋ณ„
  • ์„œ๋ฒ„ ์„ธ์…˜์ด ํ•„์š” ์—†์–ด ํ™•์žฅ์„ฑ์ด ์ข‹์Œ
  • ๋‹จ ํ† ํฐ์ด ํƒˆ์ทจ๋  ๊ฒฝ์šฐ ์œ„ํ—˜ํ•˜๋ฏ€๋กœ ๋งŒ๋ฃŒ ์‹œ๊ฐ„๊ณผ ์ €์žฅ ์œ„์น˜์— ์ฃผ์˜ํ•ด์•ผ ํ•จ
  • ์„ธ์…˜ (Session)
  • ์‚ฌ์šฉ์ž ์ƒํƒœ๋ฅผ ์„œ๋ฒ„์—์„œ ์ง์ ‘ ๊ด€๋ฆฌ
  • ํด๋ผ์ด์–ธํŠธ์—๋Š” ์„ธ์…˜ ID๋งŒ ์ €์žฅ
  • ๋น„๊ต์  ์•ˆ์ „ํ•˜์ง€๋งŒ ์„œ๋ฒ„ ์ž์›์ด ์†Œ๋ชจ๋จ
  • ํด๋Ÿฌ์Šคํ„ฐ ํ™˜๊ฒฝ์—์„œ๋Š” Redis ๋“ฑ์œผ๋กœ ์„ธ์…˜ ๊ณต์œ  ํ•„์š”

โ–ช๏ธ NextAuth ๊ธฐ๋ณธ ์„ค์ •

1. ํŒจํ‚ค์ง€ ์„ค์น˜

yarn add next-auth

2. ํ™˜๊ฒฝ๋ณ€์ˆ˜ ์ถ”๊ฐ€

NEXTAUTH_URL=http://localhost:3000
NEXTAUTH_SECRET=๋ณต์žกํ•˜๊ณ _์˜ˆ์ธก๋ถˆ๊ฐ€๋Šฅํ•œ_๋žœ๋ค๋ฌธ์ž์—ด

3. ์ธ์ฆ ํ•ธ๋“ค๋Ÿฌ(Route) ํŒŒ์ผ ์ƒ์„ฑ
App Router ๊ธฐ์ค€์œผ๋กœ app/api/auth/[...nextauth]/route.ts ๊ฒฝ๋กœ์— ํŒŒ์ผ์„ ์ƒ์„ฑํ•œ๋‹ค.

4. ์ธ์ฆ ๊ณต๊ธ‰์ž ๋ฐ ์˜ต์…˜ ์„ค์ •
์ƒ์„ฑํ•œ route ํŒŒ์ผ ๋‚ด๋ถ€์— Google, Naver ๋“ฑ ์›ํ•˜๋Š” provider๋ฅผ ๋“ฑ๋กํ•˜๊ณ  ์ฝœ๋ฐฑ ๋ฐ ์„ธ์…˜ ์„ค์ •์„ ์ถ”๊ฐ€ํ•œ๋‹ค.

5. route.ts ํŒŒ์ผ์—์„œ ์ธ์ฆ ๊ณต๊ธ‰์ž ๋“ฑ๋ก
์ƒ์„ฑํ•œ route ํŒŒ์ผ ๋‚ด๋ถ€์— Google, Naver ๋“ฑ ์›ํ•˜๋Š” provider๋ฅผ ๋“ฑ๋กํ•˜๊ณ  ์ฝœ๋ฐฑ ๋ฐ ์„ธ์…˜ ์„ค์ •์„ ์ถ”๊ฐ€ํ•œ๋‹ค. clientId, clientSecret์€ ํ™˜๊ฒฝ๋ณ€์ˆ˜์—์„œ ๋ถˆ๋Ÿฌ์˜จ๋‹ค.

GoogleProvider({
  clientId: process.env.GOOGLE_CLIENT_ID || '',
  clientSecret: process.env.GOOGLE_CLIENT_SECRET || '',
})

6. SessionProvider๋กœ ์ธ์ฆ ์ •๋ณด ์ „์—ญ ์ ์šฉ
ํด๋ผ์ด์–ธํŠธ ํ™˜๊ฒฝ์—์„œ ๋กœ๊ทธ์ธ ์ƒํƒœ๋ฅผ ์ „์—ญ์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก SessionProvider๋ฅผ ์„ค์ •ํ•œ๋‹ค. app/providers.tsx ๋˜๋Š” layout.tsx ๋“ฑ ์ตœ์ƒ์œ„ ์ปดํฌ๋„ŒํŠธ์— ๋“ฑ๋กํ•œ๋‹ค

import { SessionProvider } from 'next-auth/react'

๐Ÿ’ก ๋กœ๊ทธ์ธ ์ƒํƒœ ํ™•์ธ๊ณผ ์„ธ์…˜ ๊ด€๋ฆฌ
๋กœ๊ทธ์ธ ์—ฌ๋ถ€๋‚˜ ์‚ฌ์šฉ์ž ์ •๋ณด๋ฅผ ํ™•์ธํ•˜๋ ค๋ฉด useSession() ํ›…์„ ์‚ฌ์šฉํ•˜๋ฉด ๋œ๋‹ค.
status๋กœ ์ธ์ฆ ์ƒํƒœ๋ฅผ ํŒ๋‹จํ•˜๊ณ  session ๊ฐ์ฒด๋กœ ์‚ฌ์šฉ์ž ์ •๋ณด์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋‹ค.

const { data: session, status } = useSession()

if (status === 'authenticated') {
  return <p>Welcome, {session.user.email}</p>
}

return <Link href="/api/auth/signin">Sign in</Link>

๐Ÿ’ก ์ธ์ฆ์ด ํ•„์š”ํ•œ ํŽ˜์ด์ง€ ๋ณดํ˜ธํ•˜๊ธฐ (Middleware ์„ค์ •)
NextAuth๋Š” middleware๋ฅผ ํ†ตํ•ด ํŠน์ • ๊ฒฝ๋กœ์— ๋กœ๊ทธ์ธ๋œ ์‚ฌ์šฉ์ž๋งŒ ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋„๋ก ์ œ์–ดํ•  ์ˆ˜ ์žˆ๋‹ค. (ex. ๋งˆ์ดํŽ˜์ด์ง€, ๊ฐœ์ธ์ •๋ณด ์ˆ˜์ • ๋“ฑ)


  1. .env์— NEXTAUTH_SECRET์„ ๋ฐ˜๋“œ์‹œ ์„ค์ •
  2. /src/middleware.ts ํŒŒ์ผ ์ƒ์„ฑ ํ›„ ์•„๋ž˜์™€ ๊ฐ™์ด ์ž‘์„ฑ
// middleware.ts
export { default } from 'next-auth/middleware'

export const config = {
  matcher: ['/users/mypage', '/users/info', '/users/edit'],
}

โ–ช๏ธ NextAuth + Prisma๋กœ ์‚ฌ์šฉ์ž ์ •๋ณด DB์— ์ž๋™ ์ €์žฅํ•˜๊ธฐ

NextAuth๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ ์ธ์ฆ ์ƒํƒœ๋งŒ ๊ด€๋ฆฌํ•˜์ง€๋งŒ, PrismaAdapter๋ฅผ ํ•จ๊ป˜ ์‚ฌ์šฉํ•˜๋ฉด ์‚ฌ์šฉ์ž ๊ณ„์ •, ์†Œ์…œ ๊ณ„์ •, ์„ธ์…˜ ์ •๋ณด๋ฅผ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ž๋™์œผ๋กœ ์ €์žฅํ•  ์ˆ˜ ์žˆ๋‹ค. ์ด๋•Œ ํ•„์š”ํ•œ ๋ชจ๋ธ์€ User, Account, Session, VerificationToken์ด๋ฉฐ, schema.prisma ํŒŒ์ผ์— ์ •์˜ํ•œ ๋’ค prisma migrate๋กœ ๋ฐ˜์˜ํ•˜๋ฉด ๋œ๋‹ค.

1. Prisma Adapter ์„ค์น˜

yarn add @auth/prisma-adapter

2. Adapter ์ ์šฉ
next-auth ์„ค์ • ํŒŒ์ผ(route.ts)์—์„œ ์•„๋ž˜์ฒ˜๋Ÿผ adapter๋ฅผ ๋“ฑ๋กํ•˜๋ฉด ๋œ๋‹ค.

import { PrismaAdapter } from '@auth/prisma-adapter'
import prisma from '@/db'

export const authOptions = {
  adapter: PrismaAdapter(prisma),
  providers: [...],
  ...
}

3. Prisma ์Šคํ‚ค๋งˆ ์ •์˜ ๋ฐ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜
PrismaAdapter๋ฅผ ์‚ฌ์šฉํ•˜๋ ค๋ฉด next-auth๊ฐ€ ์š”๊ตฌํ•˜๋Š” 4๊ฐ€์ง€ ๋ชจ๋ธ์„ schema.prisma์— ์ •์˜ํ•ด์•ผ ํ•œ๋‹ค. (์ „์ฒด ์Šคํ‚ค๋งˆ ๊ตฌ์กฐ๋Š” ๊ณต์‹ ๋ฌธ์„œ์—์„œ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.)


โ–ช๏ธ Google ๋กœ๊ทธ์ธ ์—ฐ๋™ํ•˜๊ธฐ

๊ตฌ๊ธ€ ๊ฐœ๋ฐœ์ž ์ฝ˜์†”์—์„œ ํด๋ผ์ด์–ธํŠธ๋ฅผ ์ƒ์„ฑํ•˜๊ณ , ๋ฐœ๊ธ‰๋ฐ›์€ Client ID, Client Secret์„ .env์— ๋“ฑ๋กํ•œ๋‹ค. NextAuth ์„ค์ • ํŒŒ์ผ(route.ts)์—์„œ GoogleProvider๋ฅผ ์ถ”๊ฐ€ํ•˜๋ฉด ์—ฐ๋™์ด ์™„๋ฃŒ๋œ๋‹ค.

1. Google Cloud ํด๋ผ์ด์–ธํŠธ ์ƒ์„ฑ
ํ”„๋กœ์ ํŠธ ์ƒ์„ฑ ํ›„ ์‚ฌ์šฉ์ž ์ธ์ฆ ์ •๋ณด > OAuth 2.0 ํด๋ผ์ด์–ธํŠธ ID ์ƒ์„ฑ ํ›„ ์ƒ์„ฑ๋œ Client ID์™€ Client Secret์„ .env์— ์ถ”๊ฐ€ํ•œ๋‹ค.

GOOGLE_CLIENT_ID=...
GOOGLE_CLIENT_SECRET=...

์Šน์ธ๋œ ๋ฆฌ๋””๋ ‰์…˜ URI์— ์•„๋ž˜ ๊ฒฝ๋กœ ์ถ”๊ฐ€ํ•œ๋‹ค.

http://localhost:3000/api/auth/callback/google

2. next-auth์— GoogleProvider ๋“ฑ๋ก
app/api/auth/[...nextauth]/route.ts ํŒŒ์ผ์— ์•„๋ž˜ ์ฝ”๋“œ๋ฅผ ์ถ”๊ฐ€ํ•œ๋‹ค.

export const authOptions = {
  providers: [
    GoogleProvider({
      clientId: process.env.GOOGLE_CLIENT_ID || '',
      clientSecret: process.env.GOOGLE_CLIENT_SECRET || '',
    }),
  ],
  ...
}

  • adapter: PrismaAdapter(prisma)๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋กœ๊ทธ์ธํ•œ ์‚ฌ์šฉ์ž์˜ ์ •๋ณด๊ฐ€ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ž๋™์œผ๋กœ ์ €์žฅ๋œ๋‹ค. User, Account, Session ํ…Œ์ด๋ธ”์— ํ•„์š”ํ•œ ์ •๋ณด๊ฐ€ ๊ฐ๊ฐ ๋“ค์–ด๊ฐ€๊ธฐ ๋•Œ๋ฌธ์— ๋ณ„๋„๋กœ ์‚ฌ์šฉ์ž ์ •๋ณด๋ฅผ ๊ด€๋ฆฌํ•˜๋Š” ๋กœ์ง์„ ์ž‘์„ฑํ•˜์ง€ ์•Š์•„๋„ ๋œ๋‹ค.
  • ๊ตฌ๊ธ€ ๋กœ๊ทธ์ธ์„ ์ ์šฉํ•˜๋ ค๋ฉด GoogleProvider๋ฅผ ์ถ”๊ฐ€ํ•ด์•ผ ํ•œ๋‹ค. ๊ตฌ๊ธ€ ๊ฐœ๋ฐœ์ž ์ฝ˜์†”์—์„œ ๋ฐœ๊ธ‰๋ฐ›์€ clientId์™€ clientSecret ๊ฐ’์„ ์„ค์ •ํ•˜๋ฉด ๋œ๋‹ค.
  • NextAuth๋Š” ๊ธฐ๋ณธ ๋กœ๊ทธ์ธ ํ™”๋ฉด๋„ ์ œ๊ณตํ•œ๋‹ค. ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€๋ฅผ ๊ตฌ์„ฑํ•œ /users/signin ํŽ˜์ด์ง€๋ฅผ ๋งŒ๋“ค์–ด ์—ฐ๊ฒฐํ–ˆ๋‹ค.
  • ๊ธฐ๋ณธ์ ์œผ๋กœ NextAuth๋Š” ๋กœ๊ทธ์ธํ•œ ์‚ฌ์šฉ์ž์˜ ์ด๋ฉ”์ผ๊ณผ ์ด๋ฆ„ ์ •๋„๋งŒ ์„ธ์…˜์— ๋‹ด์•„์ค€๋‹ค. DB์— ์ €์žฅ๋œ ์‚ฌ์šฉ์ž ID๋„ ํด๋ผ์ด์–ธํŠธ์—์„œ ์‚ฌ์šฉํ•  ํ•„์š”๊ฐ€ ์žˆ์–ด callback์„ ์„ค์ •ํ•˜์—ฌ ์„ธ์…˜์— user.id ๊ฐ’์„ ์ถ”๊ฐ€๋กœ ํฌํ•จ์‹œ์ผฐ๋‹ค.

๋กœ๊ทธ์ธ ํŽ˜์ด์ง€์—์„œ๋Š” signIn() ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•ด ์‚ฌ์šฉ์ž๊ฐ€ ๋กœ๊ทธ์ธ ๋ฒ„ํŠผ์„ ํด๋ฆญํ•˜๋ฉด OAuth ์ธ์ฆ์ด ์‹œ์ž‘๋˜๋„๋ก ๊ตฌ์„ฑํ–ˆ๋‹ค. ๋กœ๊ทธ์ธ์— ์„ฑ๊ณตํ•˜๋ฉด, ์ง€์ •ํ•œ callbackUrl(์—ฌ๊ธฐ์„œ๋Š” ๋ฉ”์ธ ํŽ˜์ด์ง€ /)๋กœ ์ž๋™ ์ด๋™ํ•˜๋„๋ก ์ฒ˜๋ฆฌํ–ˆ๋‹ค.


โ–ช๏ธ NaverยทKakao ๋กœ๊ทธ์ธ ์—ฐ๋™ํ•˜๊ธฐ

๋„ค์ด๋ฒ„์™€ ์นด์นด์˜ค ์†Œ์…œ ๋กœ๊ทธ์ธ๋„ ํ•จ๊ป˜ ๊ตฌํ˜„ํ–ˆ๋‹ค. ๊ฐ ํ”Œ๋žซํผ์˜ ๊ฐœ๋ฐœ์ž ์‚ฌ์ดํŠธ(NAVER Developers, Kakao Developers)์— ์•ฑ์„ ๋“ฑ๋กํ•œ ๋’ค, ๋ฐœ๊ธ‰๋ฐ›์€ Client ID, Secret, Redirect URI ์„ค์ • ํ›„ provider ์˜ต์…˜์— ์ž‘์„ฑํ•ด์ฃผ๋ฉด ๊ตฌ๊ธ€ ๋กœ๊ทธ์ธ๊ณผ ๋™์ผํ•œ ๋ฐฉ์‹์œผ๋กœ ์—ฐ๋™ํ•  ์ˆ˜ ์žˆ๋‹ค.

๋‹ค๋งŒ ํ”Œ๋žซํผ๋งˆ๋‹ค ๋ฐ˜ํ™˜๋˜๋Š” ๋ฐ์ดํ„ฐ ๊ตฌ์กฐ๊ฐ€ ์กฐ๊ธˆ์”ฉ ๋‹ฌ๋ผ prisma์˜ ์Šคํ‚ค๋งˆ๋„ ๊ทธ์— ๋งž๊ฒŒ ์กฐ์ •์ด ํ•„์š”ํ•˜๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด ์นด์นด์˜ค์˜ ๊ฒฝ์šฐ refresh_token_expires_in๊ณผ ๊ฐ™์€ ํ•„๋“œ๊ฐ€ ๋”ฐ๋กœ ์ „๋‹ฌ๋˜๊ธฐ ๋•Œ๋ฌธ์— ํ•ด๋‹น ๊ฐ’์„ ์ €์žฅํ•˜๊ณ ์ž ํ•œ๋‹ค๋ฉด Account ๋ชจ๋ธ์— ํ•ด๋‹น ํ•„๋“œ๋ฅผ ์ถ”๊ฐ€ํ•ด์ค˜์•ผ ํ•œ๋‹ค. ์ด์ฒ˜๋Ÿผ ๊ฐ ์†Œ์…œ ๋กœ๊ทธ์ธ ์ œ๊ณต์ž์˜ ๋ฐ˜ํ™˜ ๊ฐ’์— ๋”ฐ๋ผ ์Šคํ‚ค๋งˆ๋ฅผ ์กฐ์ •ํ•ด์•ผ ํ•˜๋ฏ€๋กœ, ๊ณต์‹ ๋ฌธ์„œ๋ฅผ ์ฐธ๊ณ ํ•ด ํ•„๋“œ ์ •์˜๋ฅผ ๋ช…ํ™•ํžˆ ํ•ด๋‘๋Š” ๊ฒƒ์ด ์ค‘์š”ํ•˜๋‹ค.

์‹ค์ œ๋กœ Supabase์— ์ƒ์„ฑ๋œ ์‚ฌ์šฉ์ž ๋ฐ ๋กœ๊ทธ์ธ ๊ด€๋ จ ํ…Œ์ด๋ธ”์€ ์•„๋ž˜์™€ ๊ฐ™๋‹ค. ์†Œ์…œ ๋กœ๊ทธ์ธ(Google, Naver, Kakao ๋“ฑ)์„ ์ง„ํ–‰ํ•œ ํ›„ ์‚ฌ์šฉ์ž ์ •๋ณด๊ฐ€ User, Account, Session ํ…Œ์ด๋ธ”์— ์ž๋™์œผ๋กœ ์ €์žฅ๋˜๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์—ˆ๊ณ , Prisma Adapter๋ฅผ ํ†ตํ•ด NextAuth์™€์˜ ์—ฐ๋™๋„ ๋ฌธ์ œ์—†์ด ์ž˜ ์ž‘๋™ํ–ˆ๋‹ค. ๐Ÿคฉ


โ–ช๏ธ ์„ธ์…˜ ๊ธฐ๋ฐ˜ ๋งˆ์ดํŽ˜์ด์ง€ ๊ตฌ์„ฑํ•˜๊ธฐ

๋กœ๊ทธ์ธํ•œ ์‚ฌ์šฉ์ž์˜ ์ •๋ณด๋ฅผ ์„œ๋ฒ„์—์„œ ์•ˆ์ „ํ•˜๊ฒŒ ์กฐํšŒํ•  ์ˆ˜ ์žˆ๋„๋ก API๋ฅผ ๊ตฌํ˜„ํ–ˆ๋‹ค.
getServerSession()์„ ํ†ตํ•ด ํ˜„์žฌ ์„ธ์…˜ ์ •๋ณด๋ฅผ ๊ฐ€์ ธ์˜ค๊ณ , ์ธ์ฆ๋œ ์‚ฌ์šฉ์ž๋ผ๋ฉด Prisma๋ฅผ ์‚ฌ์šฉํ•ด ์‚ฌ์šฉ์ž ์ •๋ณด์™€ ์—ฐ๊ฒฐ๋œ ์†Œ์…œ ๊ณ„์ •(Account) ์ •๋ณด๊นŒ์ง€ ํ•จ๊ป˜ ์กฐํšŒํ•œ๋‹ค.

์‘๋‹ต์€ NextResponse.json()์œผ๋กœ ๋ฐ˜ํ™˜ํ•˜๋ฉฐ ์ธ์ฆ๋˜์ง€ ์•Š์€ ๊ฒฝ์šฐ์—๋Š” 401 Unauthorized ์ƒํƒœ ์ฝ”๋“œ์™€ ํ•จ๊ป˜ ์—๋Ÿฌ ๋ฉ”์‹œ์ง€๋ฅผ ์ „๋‹ฌํ•œ๋‹ค.

ํด๋ผ์ด์–ธํŠธ ์ธก์—์„œ๋Š” useSession() ํ›…์„ ํ™œ์šฉํ•ด ๋กœ๊ทธ์ธ๋œ ์‚ฌ์šฉ์ž ์ •๋ณด๋ฅผ ๊ฐ€์ ธ์˜ค๊ณ , ๋งˆ์ดํŽ˜์ด์ง€ ์ƒ๋‹จ์— ์‚ฌ์šฉ์ž ์ด๋ฆ„๊ณผ ์ด๋ฉ”์ผ์„ ํ‘œ์‹œํ–ˆ๋‹ค.

๋งˆ์ดํŽ˜์ด์ง€์—๋Š” ์‚ฌ์šฉ์ž ์ •๋ณด๋ฅผ ์ƒ์„ธํžˆ ๋ณด์—ฌ์ฃผ๋Š” ๊ฐœ์ธ์ •๋ณด ํŽ˜์ด์ง€๋ฅผ ๋”ฐ๋กœ ๊ตฌ์„ฑํ–ˆ๋‹ค.
์ด ํŽ˜์ด์ง€๋Š” React Query๋ฅผ ํ™œ์šฉํ•ด ์„œ๋ฒ„์—์„œ ์‚ฌ์šฉ์ž ๋ฐ์ดํ„ฐ๋ฅผ ๋น„๋™๊ธฐ์ ์œผ๋กœ ๋ถˆ๋Ÿฌ์˜ค๋ฉฐ enabled ์˜ต์…˜์„ ์„ค์ •ํ•ด ๋กœ๊ทธ์ธ๋œ ๊ฒฝ์šฐ์—๋งŒ API๊ฐ€ ํ˜ธ์ถœ๋˜๋„๋ก ํ–ˆ๋‹ค.

const { data: user } = useQuery('user', fetchUser, {
  enabled: status === 'authenticated',
})

๋ถˆ๋Ÿฌ์˜จ ์ •๋ณด๋Š” ์ด๋ฆ„, ์ด๋ฉ”์ผ, ํ”„๋กœํ•„ ์ด๋ฏธ์ง€, ์ฃผ์†Œ, ์ „ํ™”๋ฒˆํ˜ธ, ๋กœ๊ทธ์ธํ•œ SNS ๋“ฑ์œผ๋กœ ๊ตฌ์„ฑ๋˜๋ฉฐ ํŽ˜์ด์ง€ ํ•˜๋‹จ์—๋Š” ๋กœ๊ทธ์•„์›ƒ ๊ธฐ๋Šฅ๋„ ํ•จ๊ป˜ ์ œ๊ณตํ•œ๋‹ค. signOut() ํ•จ์ˆ˜ ํ˜ธ์ถœ ์‹œ ์„ธ์…˜์ด ์ข…๋ฃŒ๋˜๊ณ , ๋ฉ”์ธ ํŽ˜์ด์ง€(/)๋กœ ์ž๋™ ์ด๋™๋œ๋‹ค.


โ–ช๏ธ ์‚ฌ์šฉ์ž ์ •๋ณด ์ˆ˜์ • ํผ ๊ตฌํ˜„ํ•˜๊ธฐ

  • tailwindcss Form Layouts
    ๊ธฐ๋ณธ์ ์ธ ํผ UI๋Š” Tailwind CSS์—์„œ ์ œ๊ณตํ•˜๋Š” Form Layout ์˜ˆ์ œ๋ฅผ ์ฐธ๊ณ ํ•ด ๊ตฌํ˜„ํ–ˆ๋‹ค. ๊ทธ๋ฆฌ๋“œ ๊ธฐ๋ฐ˜์œผ๋กœ ๊ตฌ์„ฑ๋œ ์ž…๋ ฅ ํ•„๋“œ์™€ ์„น์…˜ ์ œ๋ชฉ ์Šคํƒ€์ผ๋ง ๋“ฑ์€ ์œ ํ‹ธ๋ฆฌํ‹ฐ ํด๋ž˜์Šค๋ฅผ ๊ทธ๋Œ€๋กœ ํ™œ์šฉํ•ด ๋น ๋ฅด๊ฒŒ ๊ตฌ์ถ•ํ•  ์ˆ˜ ์žˆ์—ˆ๋‹ค.

์กฐํšŒ๋œ ์‚ฌ์šฉ์ž ์ •๋ณด๋Š” useEffect๋ฅผ ํ†ตํ•ด useState๋กœ ๊ตฌ์„ฑ๋œ ํผ ์ƒํƒœ(name, email, phone, address)์— ์ดˆ๊ธฐ๊ฐ’์œผ๋กœ ํ• ๋‹นํ–ˆ๋‹ค.
API ํ˜ธ์ถœ ์‹œ์—๋Š” refetchOnMount: false ์˜ต์…˜์„ ์‚ฌ์šฉํ•ด ๋ฆฌ๋ Œ๋”๋ง ์‹œ ์ค‘๋ณต ํ˜ธ์ถœ์„ ๋ฐฉ์ง€ํ–ˆ๋‹ค.

์‚ฌ์šฉ์ž๊ฐ€ ํ•„๋“œ๋ฅผ ์ˆ˜์ •ํ•˜๋ฉด onChange ์ด๋ฒคํŠธ์—์„œ ๊ฐ ํ•„๋“œ์˜ ์ƒํƒœ๋ฅผ ์—…๋ฐ์ดํŠธํ•˜๊ณ , '์ˆ˜์ •ํ•˜๊ธฐ' ๋ฒ„ํŠผ ํด๋ฆญ ์‹œ axios.put()์œผ๋กœ /api/users์— ์ˆ˜์ •๋œ ๋ฐ์ดํ„ฐ๋ฅผ ์ „์†กํ•œ๋‹ค.

์š”์ฒญ์ด ์„ฑ๊ณตํ•˜๋ฉด react-hot-toast๋กœ ์„ฑ๊ณต ๋ฉ”์‹œ์ง€๋ฅผ ๋„์šฐ๊ณ , useRouter().replace()๋ฅผ ํ†ตํ•ด ๋‹ค์‹œ ๊ฐœ์ธ์ •๋ณด ํŽ˜์ด์ง€(/users/info)๋กœ ์ด๋™์‹œํ‚จ๋‹ค.

profile
์ฐจ๊ณก์ฐจ๊ณก ์Œ“์•„๋‘๊ธฐ ๐Ÿ’ญ

0๊ฐœ์˜ ๋Œ“๊ธ€