Next HTTPOnly Cookie

GwangSoo·2024년 11월 28일
2

개인공부

목록 보기
14/34
post-thumbnail

요즘 대부분의 서비스들은 로그인을 통한 유저들을 확보한다. 그리고 JWT 토큰을 통한 유저 인증을 진행하고 있다.

그렇다면 프론트엔드에서는 토큰을 어디에 저장하고 어떻게 사용을 할까?

이번 글에서는 Next 관점에서 토큰을 어디에 저장하고 어떻게 사용하면 좋을지에 대해 알아보겠다.

이 글은 Next 15를 기준으로 작성했습니다.

브라우저 스토리지

토큰을 저장하는 가장 간단한 방법으로는 브라우저의 스토리지localStorage 혹은 sessionStorage에 저장하는 것이다.

아래는 localStorage를 통한 간단한 유저 인증 예시이다.

// /app/page.tsx
"use client";

import { useEffect } from "react";

export default function Home() {
	...
	
  useEffect(()=>{
    const fetchData = async() => {
      const token = window.localStorage.getItem("token") || undefined;
      const response = await fetch("url", {
        method: "POST",
        headers: {
          Authorization: `Bearer ${token}`}
      });
      ...
    }
    fetchData();
  },[])
  
  return (
		...
  );
}

위에서 볼 수 있듯 localStorage에서 토큰을 가져와서 헤더에 넣는 식으로 간단하게 구현이 가능하다. 하지만 이는 아래와 같은 단점이 있다.

  1. 보안

브라우저 스토리지를 이용하기 때문에 개발자 모드로 확인할 수 있어서 보안에 취약하다.

  1. 클라이언트 컴포넌트에서 동작

windowlocalStorage에 접근해야 하기 때문에 클라이언트 컴포넌트로 작성해야 한다. 이는 Next의 서버 컴포넌트 이점을 살리지 못하게 된다.

또 다른 방법으로는 cookie가 있다. Next에서 cookie는 서버 액션에서만 동작하는 특성을 갖고 있다.

아래는 cookie를 이용한 유저 인증 예시이다.

// /app/page.tsx
"use client";

import { useEffect } from "react";

export default function Home() {
	...
	const handleFetchUserData = async () => {
		const res = await fetch('/api/user');
		...
	}
  return (
		...
		<button onClick={handleFetchUserData}>Fetch user data</button>
  );
}
// /app/api/user/route.ts
import { cookies } from "next/headers";
import { NextRequest, NextResponse } from "next/server";

export async function GET(req: NextRequest) {
  const cookieStore = await cookies();
  const token = cookieStore.get("token") || undefined;
  const response = await fetch("url", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${token}`}
  });

  return NextResponse.json({ status: 200, token });
}

브라우저 스토리지와 다른 점이 있다면 서버 사이드에서 쿠키에 있는 token을 추출 후 API 요청을 보낸다는 것이다. 이는 브라우저 스토리지에서 단점이라고 언급했던 클라이언트 사이드에서의 실행을 극복했다.

하지만 쿠키는 document로 접근 및 조작을 할 수 있기에 보안에 관한 문제는 여전히 남아있다.

그렇다면 보안까지 챙기면서 서버 사이드에서 동작할 수 있게 하는 방법은 뭐가 있을까?

HTTP Cookie는 서버가 브라우저에 작은 데이터 조각을 보내놓는데, 브라우저는 이를 저장해두었다가 동일한 서버에 재요청을 할 때 이전에 저장한 데이터를 함께 전송한다.

이를 통해 서버는 동일한 브라우저에서 요청이 들어왔는지 판별할 수 있고, 사용자의 로그인 상태를 유지하는 데 사용할 수 있다.

HTTPOnly Cookie크로스 사이트 스크립팅(XSS) 공격을 방지하기 위해 사용된다. 이는 JavaScript의 document.cookie API에 접근할 수 없어 보안이 한층 강화된다.

아래는 Next에서 HTTPOnly Cookie를 사용하는 예제이다.

// /app/page.tsx
"use client";

import { useEffect } from "react";

export default function Home() {
	...
	const handleFetchUserData = async () => {
		const res = await fetch('/api/user');
		...
	}
  return (
		...
		<button onClick={handleFetchUserData}>Fetch user data</button>
  );
}
// /app/api/user/route.ts
import { NextRequest, NextResponse } from "next/server";

export async function POST(req: NextRequest) {
  const body = await req.json();
  const response = await fetch("url", {
    method: "POST",
    body: JSON.stringify(body),
  });

  if (!response.ok) {
    return NextResponse.json({ status: response.status, statusText: response.statusText });
  }

  const { data } = await response.json();

  const res = NextResponse.json({ message: "success", data });
  
  res.cookies.set("token", data.token, {
    httpOnly: true,
    secure: process.env.NODE_ENV === "production",
    sameSite: "strict",
    path: "/",
    maxAge: 60 * 60 * 24,
  });

  return res;
}

export async function GET(req: NextRequest) {
  const token = req.cookies.get("token");
  
  // token을 이용한 로직 작성
  ...
  
  return NextResponse.json(...);
}

NextRequestNextResponse는 응답 혹은 요청의 Set-Cookie 속성을 읽거나 변경할 수 있다. res.cookies 부분에 설정된 옵션들을 보면 아래와 같다.

  • httpOnly: JavaScript의 document.cookie API를 통해 접근할 수 없게 한다. 이를 통해 XSS 공격을 방지할 수 있다.
  • secure: localhost를 제외한 https 일 때만 쿠키가 전송된다.
  • sameSite: strict 값은 브라우저가 동일한 사이트 요청에만 쿠키를 전송한다. lax, none 옵션이 추가로 있다.
  • path: 요청을 보내고자 하는 url이 path에 명시된 url을 포함하고 있을 때만 쿠키가 전송된다.
    • ex) path: “/docs”인 경우
    • 일치하는 경우: /docs/docs//docs/Web//docs/Web/HTTP
    • 일치하지 않는 경우: //docsets/fr/docs
  • maxAge: 쿠키가 만료될 때까지의 시간(초 단위)이다.

상황에 맞게 옵션을 설정하여 쿠키 관리를 하면 될 것 같다.

HTTPOnly Cookie를 사용하는 것이 항상 옳을까?

Next에서 제공하는 HTTPOnly Cookie를 사용하기 위해서는 Next 자체 서버로 요청을 보내야 한다.

결국 백엔드 서버로 요청을 보내기 위해서는 Next 서버를 한 번 거쳐야 한다는 것이다.

따라서 HTTPOnly Cookie효율적으로 사용하기 위한 로직을 고민해 볼 필요가 있을 것 같다.

마무리하며

여태까지 유저 인증이 필요한 통신을 할 때는 브라우저 스토리지를 이용하여 로직을 구현했다.

보안을 생각해 보게 되면서 HTTPOnly Cookie라는 개념을 알게 되었고, 이를 추후 프로젝트에 적용해 보고자 한다.

하지만 바로 위에서 언급했듯 HTTPOnly Cookie를 사용하는 것이 무조건 옳은지, 만약 사용하게 된다면 코드를 어떻게 재사용성 있게 작성해야 할지 고민을 많이 해봐야 할 것 같다.

참고

0개의 댓글