[Next.js] middleware로 url 직접접근 막기 (rewrite, redirect 구현)
안녕하세요 오늘은 미들웨어를 통해 유저의 URL 직접 접근을 막는 법을 소개해보고자 합니다.
next 12.1.6 기준이며 현재는 12.2 이전과 이후로 쓰는 방법이 다르기 때문에 같이 포스팅하겠습니다.
URL 직접접근이란? (Direct access url)
유저가 페이지에 접근할 때 URL에 직접적으로 접근하는 경우를 말합니다.
클라이언트 단에서 이런 직접 접근을 제어하는 일은 꼭 필요한데요.
예를 들어 로그인을 해야만 접근할 수 있는 마이페이지와 같은 경우
로그인하지 않은 유저가 직접 URL로 접근을 시도하면 header에 토큰을 넣어야 하는 API요청인데 없는 상태로 넣게 되어 이슈가 있는 페이지로 보이거나, 혹은 아예 그 페이지로 접근이 되어버리는 현상이 발생할 수 있게 됩니다.
보통 이런 경우 Rewrite 를 통해 적절한 페이지로 이동시키는데요. (* 같은 도메인끼리 이동이라면 Redirect가 아닌 기존 url를 유지시키는 Rewrite를 쓰는 것이 더 적절합니다.)
이는 해당 페이지에서 사이드 이펙트로도 구현이 가능하기는 합니다.
import Head from 'next/head';
import { useRouter } from 'next/router';
import { useEffect } from 'react';
import { ROUTES } from '@constants/routes';
const AccountPage = () => {
const isSignedIn = useIsSignedIn();
const router = useRouter();
useEffect(() => {
if (!isSignedIn) {
router.replace(ROUTES.SIGNIN.MAIN);
} else return;
}, []);
return (
<>
{!isSignedIn ? (
<VStack py={['500px']}>
<Spinner />
</VStack>
) : (
<>
<Head>
<title>XXX | My Account</title>
</Head>
<HomeLayout content={<AccountPageContent />} variant={'light'} />
</>
)}
</>
);
}
export default RequestPage;
하지만 위와 같이 처리를 하게 되면 접근시 순간적으로 화면이 그려지는 현상이 있어 로그인하지 않았을 때에 대한 분기 처리를 또 해줘야 하는 단점이 있습니다.
이러한 현상은 미들웨어에서 깔끔하게 해결이 됩니다.
미들웨어 (middleware)
미들웨어(middleware)란, 처음 유저가 보낸 요청과 종착지 사이에 있는 소프트웨어입니다.
next js 로 요청이 들어오면 이 함수를 거치게 된 후 페이지 컴포넌트로 들어간다고 생각하시면 됩니다.
미들웨어는 request가 완료되기 전에 실행시킬 코드에 대해 정의할 수 있습니다. 그리고 우리는 이를 활용하여 들어오는 request에 대해 response를 덮어쓸 수도, redirecting 할 수도, header를 추가할 수도 그리고 cookie를 설정할 수도 있게 됩니다.
예시로는 접근 제어, A/B 테스팅, 리다이렉트, 브라우저 지원, 로깅 등등 다양하게 활용이 가능합니다.
Next.js는 간편한 미들웨어 구현을 제공합니다.
Next.js : middleware
파일 생성 경로 및 이름
Before
page 내의 app.ts와 같은 레벨의 디렉토리
파일 이름 _middleware.ts
After
root 또는 src폴더 안 경로 (pages와 같은 레벨의 경로)
파일 이름 middleware.ts
* middlware에서 Matcher를 활용하여 경로를 제어할 수도 있습니다. > 문서
Cookie 사용하기
유저의 로그인 상태를 알기 위해 쿠키에 저장한 토큰을 가져옵니다.
Before
const cookie = request.cookies ['@token'];
After
const cookie = request.cookies.get('@token')?. value
URL 직접 접근 제어 판단하기 위한 startWith 함수 생성
가독성을 위해 startsWithURL이라는 함수를 생성하였습니다.
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
export default function middleware(request: NextRequest) {
.
.
.
const { pathname } = request.nextUrl;
const startsWithURL = (url: string): boolean => {
return pathname.startsWith(url);
};
return NextResponse.next();
}
로그인 유무에 따른 Rewrite 설정
로그인 상태일 경우
회원가입 / 로그인 페이지에 접근할 경우> 홈으로 이동
로그인 상태가 아닐 경우
마이페이지 / 포스트 디테일 페이지 / 문의 페이지에 접근할 경우 > 로그인 페이지로 이동
+ 참고로 아예 옳지 않은 경로로 이동할 경우에는 별다른 설정 없이 404.ts 페이지로 이동시켜줍니다. 문서 참고
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
import { TokenType } from '@apis/auth/AuthApi.type';
import { ROUTES } from '@constants/routes';
export default function middleware(request: NextRequest) {
const { pathname } = request.nextUrl;
const token = request.cookies['@token'];
const jsonToken: TokenType = token ? JSON.parse(token) : null;
const isSignedIn = !!jsonToken;
const startsWithURL = (url: string): boolean => {
return pathname.startsWith(url);
};
if (!isSignedIn) {
if (
startsWithURL(ROUTES.ACCOUNT.MAIN) ||
startsWithURL(ROUTES.POST.DETAIL) ||
startsWithURL(ROUTES.INQUIRY)
) {
return NextResponse.rewrite(new URL(ROUTES.SIGNIN.MAIN, request.url));
}
} else {
if (startsWithURL(ROUTES.SIGNIN.MAIN) || startsWithURL(ROUTES.REGISTER))
return NextResponse.rewrite(new URL(ROUTES.HOME, request.url));
}
return NextResponse.next();
}
Reference