Skip to Content
Suffering builds character
아카이브9.Next.js특징인증5. Stateless 기반 세션 관리

5. Stateless 기반 세션 관리

별도의 상태를 유지하지 않는 방식으로 세션을 관리하기 위해서는 다음의 단계를 따름

  1. 세션 서명에 사용할 비밀 키 생성 및 환경 변수로 저장
  2. 세션 관리 라이브러리를 활용하여 세션 데이터를 암호화/복호화
  3. Next.js의 cookies API를 활용하여 브라우저의 쿠키값 관리

추가적으로 사용자가 복귀했을 때 세션을 새로 업데이트하거나, 로그아웃하였을 경우 세션 정보를 제거하는 등의 처리도 필요

1. 세션 서명에 사용할 비밀 키 생성

terminal
openssl rand -base64 32

→ 32자의 랜덤 문자열 생성

.env 환경 변수에 저장

.env
SESSION_SECRET=your_secret_key

생성한 키를 세션 관리에 활용

app/lib/session.js
const secretKey = process.env.SESSION_SECRET

2. 세션 관리 라이브러리를 활용하여 세션 정보 암호화/복호화

선호하는 세션 관리 라이브러리를 통해 세션 정보를 암/복호화 수행
ex. Jose, server-only 패키지 활용

이를 통해 세션 관리 로직이 서버에서만 실행될 수 있도록 보장

app/lib/session.js
import 'server-only' // 아래 코드는 서버에서만 실행되는 코드임을 보장 import { SignJWT, jwtVerify } from 'jose' const secretKey = process.env.SESSION_SECRET const encodedKey = new TextEncoder().encode(secretKey) // 토큰 암호화 export async function encrypt(payload) { return new SignJWT(payload) .setProtectedHeader({ alg: 'HS256' }) .setIssuedAt() .setExpirationTime('7d') .sign(encodedKey) } // 세션에서 꺼낸 토큰 정보를 복호화 export async function decrypt(session) { try { const { payload } = await jwtVerify(session, encodedKey, { algorithms: ['HS256'], }) return payload } catch (error) { console.log('Failed to verify session') } }
⚠️
Warning

토큰에 담길 페이로드
토큰에 담길 페이로드에는 개인 정보를 담지 않아야 함
예를 들면 연락처, 이메일 주소, 크레딧 카드 정보와 같이 식별 가능한 정보나 비밀번호와 같이 민감한 데이터

따라서 사용자 ID나 role과 같이 후속 요청 처리에 사용될 유니크한 사용자 정보와 같은 것들만 포함해야 함

3. Next.js의 cookies API를 활용하여 브라우저의 쿠키값 관리

쿠키에 세션 정보를 저장하기 위해서는 Next.js의 cookies API를 활용
이러한 쿠키 설정 작업은 서버에서 수행하며, 아래와 같은 옵션들을 적용하는 것이 추천됨

  • HttpOnly: 클라이언트 측에서 JavaScript를 통해 쿠키에 액세스할 수 없도록 방지
  • Secure: HTTPS를 통해 쿠키 전송
  • SameSite: 쿠키를 크로스사이트 요청에서 전송할 수 있는지 여부 지정
  • Max-Age or Expires: 쿠키 만료 시간 지정

서버 로직으로 쿠키를 설정하는 예시 코드

app/lib/session.js
import 'server-only' import { cookies } from 'next/headers' export async function createSession(userId) { const expiresAt = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000) const session = await encrypt({ userId, expiresAt }) const cookieStore = await cookies() cookieStore.set('session', session, { httpOnly: true, secure: true, expires: expiresAt, sameSite: 'lax', path: '/', }) }

Server Action에서는 createSession()을 호출하여 세션을 생성하고, 사용자를 적절한 페이지로 리다이렉트시킴

app/actions/auth.js
import { createSession } from '@/app/lib/session' // 회원가입 함수 export async function signup(state, formData) { // Previous steps: // 1. 입력 필드 값 검증 // 2. DB에 INSERT할 준비 // 3. INSERT 수행 // 현재 단계: // 4. 사용자 세션 생성 await createSession(user.id) // 5. Redirect user redirect('/profile') }

4. 사용자가 서비스로 돌아왔을 경우 세션 정보 갱신

사용자가 잠시 자리를 비웠다가 서비스로 돌아왔을 때에도 로그인 상태를 유지할 수 있도록 세션의 만료 시간을 연장할 수 있음

app/lib/session.js
import 'server-only' import { cookies } from 'next/headers' import { decrypt } from '@/app/lib/session' export async function updateSession() { const session = (await cookies()).get('session')?.value const payload = await decrypt(session) if (!session || !payload) { return null } const expires = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000)( await cookies() ).set('session', session, { httpOnly: true, secure: true, expires: expires, sameSite: 'lax', path: '/', }) }

5. 세션 정보 제거

사용자가 로그아웃하였을 경우 세션 정보를 제거해야 함

app/lib/session.js
import 'server-only' import { cookies } from 'next/headers' export async function deleteSession() { const cookieStore = await cookies() cookieStore.delete('session') }

로그아웃 함수에서 활용

app/actions/auth.js
import { cookies } from 'next/headers' import { deleteSession } from '@/app/lib/session' export async function logout() { deleteSession() redirect('/login') }
Last updated on