전역 변수 window 접근과 Next.js

HY·2022년 7월 1일
2

next-js

목록 보기
1/1
post-thumbnail

발단

프로젝트를 하면서 window의 접근 가능 여부의 차이로 Next.js의 특징을 이해하고 실습할 수 있었다.
Next.js 스터디 팀에서 발표하며 공유하면 좋을 것 같아 블로깅으로 정리하고자 한다.

React의 경우

recipe yam 프로젝트 작업 중 서버에서 로그인하고 사용자 정보를 받아왔을 때 세션 관리를 어떻게 해야 할지 고민했다.
그러다 window.sessionStorage를 사용하는 방법을 찾아냈다.

Window.sessionStorage
sessionStorage 읽기 전용 속성은 현재 출처 세션의 Storage 객체에 접근합니다. sessionStorage는 localStorage와 비슷하지만, localStorage의 데이터는 만료되지 않고, sessionStorage의 데이터는 페이지 세션이 끝날 때 제거되는 차이가 있습니다.
페이지 세션은 브라우저가 열려있는 한 새로고침과 페이지 복구를 거쳐도 남아있습니다.
페이지를 새로운 탭이나 창에서 열면, 세션 쿠키의 동작과는 다르게 최상위 브라우징 맥락의 값을 가진 새로운 세션을 생성합니다.
같은 URL을 다수의 탭/창에서 열면 각각의 탭/창에 대해 새로운 sessionStorage를 생성합니다.
탭/창을 닫으면 세션이 끝나고 sessionStorage 안의 객체를 초기화합니다.
sessionStorage에 저장한 자료는 페이지 프로토콜별로 구분합니다. 특히 HTTP(http://example.com)로 방문한 페이지에서 저장한 데이터는 같은 페이지의 HTTPS(https://example.com)와는 다른 sessionStorage에 저장됩니다.

이를 사용해 클라이언트에서 사용자 정보를 관리했다.

// login.js
    function login() {
        
        fetch('http://localhost:4000/login', {
            method: "POST"
            , headers: {
                "content-type":"application/json"
            }
            , body: JSON.stringify({
                email: email
                , password: password
            })
            , credentials: 'include'
        })
        .then(response => response.json())
        .then(data => {
          // sessionStorage에 사용자 정보 저장
            window.sessionStorage.setItem("userInfo", JSON.stringify(data.userInfo))
            navigate('/')
        })
    }
// app.js

const App = () => {
// sessionStorage에 사용자 정보가 저장되어 있다면 로그인 한 것으로 판단한다.
  const isLoggedIn = window.sessionStorage.getItem('userInfo')

  return (
    <div>
      <main>
        <Header/>
        <section>
          <Routes>
            <Route path="/" element={<Main/>}/>
            <Route path="/join" element={<Join />}/>
            <Route path="/login" element={<Login />}/>
            <Route element={isLoggedIn ? <Modify /> : <Navigate to="/"/>}  path="/modify" />
            <Route element={isLoggedIn ? <Update /> : <Navigate to="/"/>} path="/update/:id"/>
            <Route element={isLoggedIn ? <Write /> : <Navigate to="/"/>} path="/write"/>
          </Routes>
        </section>
        <Footer/>
      </main>
    </div>
  );
};
//Main.js
	// window.sessionStorage에 사용자 정보가 있다면 메인에서 사용자 닉네임 등을 뿌려준다.
    if(window.sessionStorage.getItem("userInfo") !== null) {
        userInfo = JSON.parse(window.sessionStorage.getItem('userInfo'))
    } 

Next.js의 경우

그리고 spark it 프로젝트를 시작했는데, 프론트엔드를 담당한 팀원이 jwt 토큰을 어떻게 관리할지 고민하기에 sessionStorage를 사용했던 경험을 공유했다.
그런데 Next.js에서는 React에서 사용했던 것처럼 window.sessionStorage에 접근할 수 없었다.

export default function Home() {

  window.sessionStorage.setItem("사용자 닉네임", "김코딩")
  console.log(window.sessionStorage.getItem("사용자 닉네임"))
  return (
    <div>
   
    </div>
  )
}

원인 파악

JavaScript 코드에 노출된 전역 변수 window는 현재 스크립트가 작동 중인 브라우저의 창을 나타낸다.
Next.js는 Server Side Rendering으로 서버에서 클라이언트에게 보여줄 HTML을 미리 준비해 클라이언트에게 응답해준다.
그런데 웹 페이지를 렌더링하는 초기에는 window가 선언되지 않았기 때문에 window is not defined에러가 발생했던 것이다.

대안

쿠키 사용하기

쿠키를 사용하여 토큰을 관리하는 방법이 있다.

import Cookies from 'js-cookie';

export default function Home() {

  Cookies.set("nickname", "김코딩", { expires: 1 });
  console.log(Cookies.get("nickname"))
  return (
    <div>
    </div>
  )
}

하지만 쿠키의 단점을 고려해야 한다.

  • 사이트 간 요청 위조 공격(XSRF 또는 CSRF) : CSRF 공격은 쿠키 기반 세션 처리에서만 가능합니다. 이 속성을 사용하면 Strict 또는 Lax 설정 SameSite을 사용하여 쿠키를 타사 앱으로 보낼지 여부를 결정할 수 있습니다 . 엄격한 설정은 CSRF 공격을 방지할 수 있지만 사용자에게 좋지 않은 브라우저 환경을 제공할 수도 있습니다. 예를 들어 사이트 에서 사용자가 방문할 때마다 새로운 자습서를 보여주기 위해 특정 자습서를 이미 보았는지 여부를 확인하기 위해 이름이 지정된 쿠키를 사용한다고 가정해 보겠습니다. 로 설정되어 있고 누군가 귀하의 사이트에 대한 링크를 팔로우하는 경우 첫 번째 요청에서 쿠키가 전송되지 않고 이전에 본 자습서가 표시됩니다. 이것은 덜 개인화된 사용자 경험을 만듭니다.tutorials_shownSameSiteStrict
  • 확장 문제 : 세션이 특정 서버에 연결되어 있으므로 애플리케이션을 확장할 때 문제가 발생할 수 있습니다. 부하 분산 응용 프로그램에서 로그인한 사용자가 새 서버로 리디렉션되면 기존 세션 데이터가 손실됩니다. 이를 방지하려면 세션을 공유 데이터베이스나 캐시에 저장해야 합니다. 이것은 각 상호 작용의 복잡성을 증가시킵니다.
  • API 인증에 적합하지 않음 : API는 인증된 최종 사용자를 위한 일회성 리소스를 제공하며 사용자 세션을 추적할 필요가 없습니다. 쿠키는 활성 세션을 추적하고 확인하기 때문에 이 경우 완벽하게 작동하지 않습니다. 한편 토큰은 API 엔드포인트에 대한 모든 요청에 ​​대해 고유 식별자로 인증을 제공합니다.
    쿠키가 세션 ID를 저장하는 유일한 방법은 아닙니다. 다른 옵션에는 URL 및 양식 필드가 포함됩니다. 쿠키는 이 두 가지보다 더 안전하지만 쿠키는 얼마나 안전합니까?
  • 쿠키는 HTTPS 연결에서만 안전합니다. 플래그를 적용 Secure하면 쿠키가 암호화된 HTTPS 연결을 통해서만 전송됩니다. HTTPS를 사용하면 MITM (person-in-the-middle ) 공격에서 세션 ID가 노출되는 것을 방지할 수 있습니다.
  • 앞서 언급했듯이 쿠키는 클라이언트 측 스크립트(JavaScript 또는 Visual Basic)로 조작할 수 있습니다. 이는 HttpOnly플래그를 사용하여 방지할 수 있습니다.

SessionStorage Class로 사용하기

SessionStorage를 클래스로 따로 분리하여 사용하는 방법이 있다.
setItem, getItem을 따로 만들어야 하지만, window is undefined 에러가 발생할 우려 없이 sessionStorage를 사용할 수 있다.

//SessionStorage

class SessionStorage {
    constructor() {}
  
    static setItem(key, item) {
      // window의 선언 여부 확인
      if (typeof window !== "undefined") {
        sessionStorage.setItem(key, item);
      }
    }
  
    static getItem(key) {
      if (typeof window !== "undefined") {
        return sessionStorage.getItem(key);
      }
      return null;
    }
  
    static removeItem(key) {
      if (typeof window !== "undefined") {
        sessionStorage.removeItem(key);
      }
    }
  }
  
  export default SessionStorage;
import SessionStorage from "../utils/SessionStorage"

export default function Home() {

  SessionStorage.setItem("nickname", "김코딩")
  console.log(SessionStorage.getItem("nickname")) // 김코딩

  return (
    <div>
    </div>
  )
}

참고 문헌

https://developer.mozilla.org/ko/docs/Web/API/Window/sessionStorage
https://blog.sethcorker.com/question/how-to-solve-referenceerror-next-js-window-is-not-defined/
https://developer.mozilla.org/ko/docs/Web/API/Window
https://velog.io/@wnsh27/Next.js-window-is-not-defined-%ED%95%B4%EA%B2%B0%ED%95%98%EA%B8%B0
https://all-dev-kang.tistory.com/entry/Next-localstorage%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%98%EB%8A%94-%EB%B0%A9%EB%B2%95
https://developer.okta.com/blog/2022/02/08/cookies-vs-tokens
https://all-dev-kang.tistory.com/entry/Next-localstorage%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%98%EB%8A%94-%EB%B0%A9%EB%B2%95

profile
사실은 공부를 비밀스럽게 하고 싶었다

0개의 댓글