Next.js/router url 접근 방지 시도기

MM·2022년 12월 25일

make it worth

목록 보기
3/10
post-thumbnail

이 시도기는 나의 러버덕 UJ님과 함께했습니다.👍

Next.js에서 로그인하지 않아 토큰이 없는 경우 로그인페이지로 리다이렉트시키는 방법에 대해 시도한 여러 사항들을 정리했다.

시도1. next.config에서 redirect 설정하기

next.config에서 다음과 같이 사용하는 페이지들 각각에 redirect를 설정해 임의로 url조정이 불가능하게 했다.

  async redirects() {
    return [
      {
        source: "/signup",
        destination: "/",
        permanent: true,
      },
    ];
  },

결론부터 말하자면 실패! 다음과 같은 문제사항이 있었다.

  • url뒤 쿼리가 주소창에 남는다...!!!

  • 로그인 되어있는 유저 또한 url 직접조작이 제한된다

시도2. Recoil로 isLogin토큰 확인

시도2-1. _app단에서 useEffect로 isLogin 값 넣어주기

우선은 적어뒀지만, 솔직히 이 방법은 쓰고싶지 않았다..! _app은 언제나 꺠끗한 상태로 유지하고 싶다!!
uj님이 주신 의견인데, 노드의 최상단이 업데이트되면 하위에 있는 노드들도 렌더링이 발생하기 때문에 가장 상단에서 상태를 계속 확인하는 것은 불필요한 렌더링을 유발할 수도 있다는 점이었다. 🤔 일리가..있어!

시도2-2. isLogin get마다 useQuery로 값 가져오기

recoil의 selector에서 get함수를 이용하는 방법!
useEffect처럼 구독하고 있는 애를 부를 때 특정 동작 실행하는 방법을 응용하여 login이 호출될 때마다 해당 상태를 업데이트하고자 했으나...
결론적으로 실패했다. 서버 사이드에서 Localstorage에 접근하지 못하기 때문..!

test selector({
	key:"Logintest",
	get:({get})=>{
		const login=get(isLoginAtom);  //login에 isLoginAtom값이 담김.
		if(!login){
			const getLoginState=getLocalStorageAccessToken();
		}
	}
})

시도2-3. recoil의 Local Storage Persistence사용하기

https://recoiljs.org/docs/guides/atom-effects/


let localStorage = typeof window !== `undefined` ? window.localStorage : null;

const localStorageEffect =
  () =>
  ({ setSelf }) => {
    if (localStorage)
      localStorage.getItem("starters_access") ? setSelf(true) : setSelf(false);
    else setSelf(false);
  };

export const isLoginAtom = atom({
  key: "Login",
  default: false,
  effects: [localStorageEffect()],
});

에러

서버사이드에서 pre-rendering된 React 트리와 브라우저에서 처음 rendering되는 React 트리가 달랐기 때문에 발생한 에러라고 하는데.. 아래 상황과 같다

서버: 우리는 localstroage없는데? → 그럼 localstroage 임시로 생성해서 써

클라이언트: 내 localstroage 들어갈 곳이 없잖아!! → 아오

이유를 찾았다!!!!!!!!!!!!!!!!!!!

그렇다. 애초에 서버 사이드에서 사용할 수 없었던 방법.. 공식문서를 잘 읽자.

2-4. 로컬스토리지대신 쿠키 쓰기

Next.js에 쿠키 라이브러리가 있다는 것과 언젠가 보안을 위해 로컬 스토리지에서 쿠키로 전환할 계획이었다는 점에서 착안하여, 지금 로컬스토리지에서 쿠키로 전환하기로 했다!

<aside>
📌 **cookies-next**

[cookies-next](https://www.npmjs.com/package/cookies-next)

</aside>

그러나 매 페이지마다 useEffect로 쿠키의 존재 유무를 초반에 확인해주어야 했고, 이로 인한 하이드레이션 또한 발생되었다..
더 효율적인 방법을 찾아보고자 시도한 결과.

2-5.next/route로 url변경 감지할 때마다 검사하기

Router.events.on("routeChangeStart", url => {
    getCookieAccessToken() ? setIsLogin(true) : setIsLogin(false);
  });

실패!
router.replace나 router.push를 이용한 직접이동 외에 url 변경이동은 감지하지 못하는 것 같다..

2-6. 미들웨어 사용하기

미들웨어란?

API의 요청-응답 사이클 직후 한번 거쳐가는 함수!
여기서 api란, 우리가 axios나 fetch로 호출하는 것 외에 next 내부에서 작동하는 모든 api를 포함하고 있는 것 같다!

let express = require('express')
let app = express();
 
app.get이나_post같은_http메서드("url주소", 
	미들웨어_실행함수(req, res, 다음_미들웨어_실행함수){
		다음_미들웨어_실행함수();
});

app.listen(3000); //소켓과 비슷한 듯?

미들웨어에는 여러 유형이 있는데, 이중에서 router level 미들웨어를 이용하여 url직접접근을 방지할 것이다.

여기서 잠깐

미들웨어는 https가 아니라 http 통신이고 아이피 gps를 가져오는 기능도 있다던데..
그럼 우리가 지금 문제삼고 있는 도메인이 없어 https↔http간 통신이 불가능한 문제를 해결할 수 있지 않을까?

👍 해결할 수 있을 것 같다! 외부 api를 사용하는 방법이다. https://rapidapi.com/blog/geolocation-backend-node-express/

라우터 레벨 미들웨어

라우터 레벨 미들웨어는 특정 루트 url을 기점으로 자동 동적 라우팅 처리가 가능하다!

let app=express();
let router=express.Router();

router.use("/", function (req,res){ 로직 처리이이이 });

우선 공식문서에서 권고한 방법인 pages폴더 내에 _middleware.ts 파일을 추가하는 방법을 사용했는데.. 안된다????

사용법이 바뀌었다……😳 그것도 꽤 많이…..

이제 pages폴더 옆에 _빼고 둔다고 한다.

거기다 response에 일반적으로 붙는 body가 없어졌다고 한다.

rewrite나 redirect를 써서 접근한다고 하는데….으음..써봐야 알 것 같다.

이렇게 한 이유가 CSR와 SSR 두쪽 다에서 동일한 안전성을 가지게 하기 위해서라고 한다.

솔직히 아직 뭔 소린지 모르겠음.🤔

쿠키 사용법도 아래처럼 바뀌었다. 이건 next의 쿠키 라이브러리와 가능한 같은 구조를 하게 하려는 시도인 것 같아서 좋은 개선사항인 듯?

브라우저 콘솔이 아니었다

export const config = {
  matcher: ["/notice", "/community/:path"],
}; //매처가 없으면, 위처럼 우르르 뜬다. 매처가 있으면 매처에 해당하는 값만 뜸!

일단 request가 뭔지 찍어봤는데.. 꽤 쓸모 있는 정보를 많이 가져온다!

다만 이미지나 폰트 등 외부에서 가져온 것들이 url에 딸려온다...

{
  cookies: RequestCookies {},
  geo: {},
  ip: undefined,
  nextUrl: {
  href: 'http://localhost:3000/image/login%20button.png',
  origin: 'http://localhost:3000',
  protocol: 'http:',
  username: '',
  password: '',
  host: 'localhost:3000',
  hostname: 'localhost',
  port: '3000',
  pathname: '/image/login%20button.png',
  search: '',
  searchParams: URLSearchParams {  },
  hash: ''
},
  url: 'http://localhost:3000/image/login%20button.png',
  bodyUsed: false,
  cache: 'default',
  credentials: 'same-origin',
  destination: '',
  headers: {
  accept: 'image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8',
  accept-encoding: 'gzip, deflate, br',
  accept-language: 'ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7',
  connection: 'keep-alive',
  host: 'localhost:3000',
  if-none-match: 'r+ecDu9TlgjkME2j6YFlpWEqCViZNZynbt9ysGoothY=',
  referer: 'http://localhost:3000/',
  ...
},
  integrity: '',
  keepalive: false,
  method: 'GET',
  mode: 'cors',
  ...
}
}

png를 삭제하면 이번엔 폰트가 주소에 달라붙음...

notice페이지는 이동시 다음과 같이 뜬다.

{
  cookies: RequestCookies {},
  geo: {},
  ip: undefined,
  nextUrl: {
  href: 'http://localhost:3000/notice',
  origin: 'http://localhost:3000',
  protocol: 'http:',
  username: '',
  password: '',
  host: 'localhost:3000',
  hostname: 'localhost',
  port: '3000',
  pathname: '/notice',
  search: '',
  searchParams: URLSearchParams {  },
  hash: ''
},

따라서..

쿠키가 없으면 “/”외 모두 “/”로 리다이렉트된다 → 이 방법의 사용이 불가능해지고,

모든 페이지를 대신 매처에 넣어주어야 함이 판명되었다.

우선 당장은 pages에 있는 것들 중 login과 signup외의 것들을 넣어주었는데.. 테스트하면서 빠진 부분을 처리해야 할 것 같다.🤔

미들웨어를 써서 좋은 점은..

해당 페이지가 렌더링 되기 전에 리다이렉트가 진행되므로, 잠깐 동안의 페이지 노출도 방지할 수 있다!

우리가 고민하던 깜빡임 현상도 해결된 것이다 👍 굿~~~

profile
중요한 건 꺾여도 그냥 하는 마음

0개의 댓글