Next.js 프로젝트에서 next-auth를 사용해 인증과 세션 관리를 진행해왔습니다. next-auth는 소셜 로그인을 포함한 다양한 인증 방식을 지원하고 있어 매우 유용했지만 프로젝트를 진행하면서 중간중간 해결해야 할 문제들이 있었습니다. 커스터마이징 요구 사항을 처리할 때는 특히 더 많은 시간이 들었던 거 같습니다.
그래서 이번 프로젝트에서는 인증은 직접 or 타 라이브러리를 사용해 구현하고, 세션 관리만 iron-session을 사용하는 방향으로 전환해보기로 했습니다.
npm add iron-session
session.save(): 세션 변수를 브라우저 쿠키 스토리지에 암호화된 문자열로 저장session.destroy(): 브라우저 쿠키 스토리지에 저장된 쿠키 값을 빈 값으로 설정필수 옵션 및 선택 옵션을 세션 옵션에 정의하기 위해 session.ts를 작성합니다.
// src/lib/session.ts
import { SessionOptions } from "iron-session";
export const sessionOptions: SessionOptions = {
cookieName: "myapp_session",
password: process.env.SESSION_PASSWORD as string,
cookieOptions: {
secure: process.env.NODE_ENV === "production", // HTTPS에서만 쿠키 전송 (production일 때만 true)
httpOnly: true, // 클라이언트 JavaScript에서 쿠키 접근 차단
sameSite: "lax", // 사용자가 외부 사이트의 링크를 클릭해 사이트에 들어올 때 쿠키가 전송됨
maxAge: 2147483647, // 최대 유효기간(약 68년)
path: "/", // 쿠키가 유효한 경로 (기본값: 사이트 전체에 대해 유효)
},
};
세션 데이터 타입을 정의합니다.
// src/lib/session.ts
import { SessionOptions } from "iron-session";
export const sessionOptions: SessionOptions = {
...
};
export interface SessionData {
nickname: string;
email: string;
isLoggedIn: boolean;
}
export const defaultSession: SessionData = {
nickname: "",
email: "",
isLoggedIn: false,
};
SessionData 인터페이스를 만든 후 저는 닉네임, 이메일, 로그인 여부 필드를 추가해주었습니다.
세션이 가지고 있어야 할 사용자 데이터를 정의해 주는 과정입니다.
Next.js API Route에 세션을 가져오고,세션을 저장하는 API를 작성합니다.
// src\app\api\session\route.ts
import { defaultSession, SessionData, sessionOptions } from "@/lib/session";
import { getIronSession } from "iron-session";
import { cookies } from "next/headers";
import { redirect } from "next/navigation";
import { NextRequest } from "next/server";
export async function GET(request: NextRequest) {
const session = await getIronSession<SessionData>(cookies(), sessionOptions);
const action = new URL(request.url).searchParams.get("action");
if (action === "logout") {
session.destroy();
return redirect("/");
}
if (session.isLoggedIn !== true) {
return Response.json(defaultSession);
}
return Response.json(session);
}
export async function POST(request: NextRequest) {
const session = await getIronSession<SessionData>(cookies(), sessionOptions);
const userInfo = await request.json();
session.email = userInfo.email;
session.nickname = userInfo.nickname;
session.isLoggedIn = true;
await session.save();
return Response.redirect(`${request.nextUrl.href}`, 303);
}
action 쿼리가 logout인 경우 session.destroy()를 사용해서 쿠키를 빈값으로 만들어주도록 했습니다.
isLoggedIn가 false인 경우에는 defaultSession을, true인 경우에는 session 데이터를 반환하도록 했습니다.
클라이언트 컴포넌트에서 사용할 API 호출 함수를 작성합니다.
// src\entities\api\fetchCookieFromSession.ts
const fetchCookieFromSession = async () => {
const res = await fetch("/api/session", {
method: "GET",
});
const data = await res.json();
console.log(res, data);
return data;
};
export default fetchCookieFromSession;
// src\entities\api\saveCookieToSession.ts
const saveCookieToSession = async (email: string, nickname: string) => {
await fetch("/api/session", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ email, nickname }),
});
};
export default saveCookieToSession;
Form 컴포넌트에서 이메일 인증코드 확인 후 새로운 유저를 만들고,
쿠키에 세션에 저장하는 로직으로 작성했습니다.
const onSubmit = async (data: { verificationCode: string }) => {
try {
setIsErrorVerifying(false);
setIsVerifying(true);
const isVerified = await verifyVerificationCode(
email,
data.verificationCode,
);
if (isVerified) {
const res = await createNewUser(email, password, nickname, "local");
if (res.success) {
await saveCookieToSession(email, nickname);
incrementStep();
}
} else {
alert("인증 코드가 일치하지 않습니다. 다시 시도해 주세요.");
}
} catch (error) {
setIsErrorVerifying(true);
} finally {
setIsVerifying(false);
}
};
쿠키 스토리지에 가보면 다음과 같이 암호화된 세션 데이터가 값으로 들어간 쿠키가 잘 생성됐습니다.

세션 데이터를 잘 가져오는지 테스트를 위해 홈으로 가기 버튼에 잠시 테스트를 해 봤습니다.
<Button
className="w-full"
type="button"
variant="outline"
onClick={async () => {
console.log(await fetchCookieFromSession());
}}
>
홈으로 가기
</Button>
