5. Stateless 기반 세션 관리
별도의 상태를 유지하지 않는 방식으로 세션을 관리하기 위해서는 다음의 단계를 따름
- 세션 서명에 사용할 비밀 키 생성 및 환경 변수로 저장
- 세션 관리 라이브러리를 활용하여 세션 데이터를 암호화/복호화
- Next.js의
cookiesAPI를 활용하여 브라우저의 쿠키값 관리
추가적으로 사용자가 복귀했을 때 세션을 새로 업데이트하거나, 로그아웃하였을 경우 세션 정보를 제거하는 등의 처리도 필요
1. 세션 서명에 사용할 비밀 키 생성
terminal
openssl rand -base64 32→ 32자의 랜덤 문자열 생성
.env 환경 변수에 저장
.env
SESSION_SECRET=your_secret_key생성한 키를 세션 관리에 활용
app/lib/session.js
const secretKey = process.env.SESSION_SECRET2. 세션 관리 라이브러리를 활용하여 세션 정보 암호화/복호화
선호하는 세션 관리 라이브러리를 통해 세션 정보를 암/복호화 수행
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: 쿠키 만료 시간 지정
💡
Tip
서버 로직으로 쿠키를 설정하는 예시 코드
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