-
[Next.js] Next.js 13에서 유저의 권한에 따른 protected route / protected api Setting (권한에 따른 페이지 접근 제한)Next.js 2023. 6. 2. 12:10
들어가며
개인 프로젝트를 저번 달부터 안정화가 된 next.js 13으로 진행하고 있다.
로그인 유무에 따라 페이지 접근 권한을 제한하는 셋팅을 적용하면서, 정리한 내용을 적어본다.
12와 달라진 점에 대해서도 나중에 블로그에 적어야겠다. ✏️
목적
유저의 상태에 따라 접근 페이지 구분이 필요하다.
예를 들어, 로그인한 유저가 로그인 페이지에 접근하는 것은 올바르지않다.반대로, 로그인 하지 않은 유저가 로그인이 필요한 마이페이지에 접근하는 것도 옳지 않다.
next.js 13은 server rendering을 지원하기 때문에, protected route를 지정할 때 “어디서” 유저의 권한을 확인하는지가 중요하다.
그래서 "어디서"를 초점으로 사용할 수 있는 방법들에 대해 알아보자.
방안 1) client 단에서 페이지 컴포넌트에서 유저 권한 확인 후, redirect 판단.
전반적인 로직은 다음과 같다.
- client 단에서 user 권한 확인 전까지 loading 상태 표기
- user 권한 확인 후, redirect 판단
import useUser from '../lib/useUser'; import Layout from '../components/Layout'; const Profile = () => { // Fetch the user client-side const { user } = useUser({ redirectTo: '/login' }); // Server-render loading state if (!user || user.isLoggedIn === false) { return <Layout>Loading...</Layout>; } // Once the user request finishes, show the user return ( <Layout> <h1>Your Profile</h1> <pre>{JSON.stringify(user, null, 2)}</pre> </Layout> ); }; export default Profile;
client 에서 확인하면 페이지 접근 후 redirect 됨으로 추가적으로 loading 처리가 필요하다는 단점이 있다.
또한, next.js 에서는 data 패치와 관련된 작업은 서버단에서 처리하는 것을 강조하고 있다.
유저 상태를 클라이언트 단에서 확인한다면, 서버단에서 data 패치하는 것은 불가능하기 때문에, next.js를 사용한다면 아래에 나오는 방법들을 추천한다.
방안 2) server 단에서 페이지 진입 시, 유저 권한 확인 후 redirect 판단
전반적인 로직은 다음과 같다.
- 서버에서 유저의 권한이 있는지(authenticated) 확인
- 유저가 권한이 있다면(authenticated), data를 fetch하고, 페이지를 렌더링한다 (페이지 접근)
- 유저가 권한이 없다면(unAuthenticated), 로그인 페이지 (혹은 임의의 authenticated를 요구하지 않는 페이지)로 redirect 한다.
// protectedPageRoute.ts export default async function ProtectedPageRoute( context, redirectTo, // string route where user will be redirected if they are not authenticated getProps, // function to fetch initial props ) { const userIsAuthenticated = true // TODO: check if user is authenticated if (!userIsAuthenticated) { return { redirect: { destination: redirectTo ?? '/signin', permanent: false, } } } if (getProps) { return { props: getProps(), } } return { props: {}, } }
export const getServerSideProps = (context) => ProtectedPageRoute(context, null, async () => { // fetch props });
client 단에서 처리하는게 아니라 페이지 진입 시, 유저 권한 검사 후 fetch를 진행한다.
방안 3) server/client 단에서 api 호출시, 유저 권한 확인 후 redirect 판단
전반적인 로직은 다음과 같다.
- api 호출
- 호출되면, 유저의 권한 검사
- 유저의 권한이 없으면, 401 error
- 유저의 권한이 있으면, api 진행
export default async function ProtectedApiRoute( req, res, requestHandler, ) { const userIsAuthenticated = true // TODO: check if user is authenticated if (!userIsAuthenticated) { return res .status(401) .json({ error: { code: 'unauthorized', message: 'User is not authorized', } }) } if (requestHandler) { return requestHandler(req, res) } return res .status(400) .json({ error: { code: 'no_request_handler_specified', message: 'No request handler specified', } }) }
export default function handler(req, res) { return ApiProtectedRoute(req, res, (req, res) => { // fetch data }); }
api를 호출할 때 권한 검사를 하면, server와 client 단에서 둘 다 사용이 가능하다.
또한, 앞에서는 페이지 진입 할 때 protected route를 설정하면, 유저 권한이 없을 경우 redirect를 시키는 반면,
api를 활용하면 status 401로 return하기 때문에 redirect 방법 말고도 상황에 맞게 대처가 가능하다.
방안 4) server 단에서 페이지 진입 시 middleware를 사용해, 유저 권한 확인 후 redirect 판단
middleware는 페이지 진입 전에 중간에 검사할 수 있는 로직을 추가할 수 있게 해준다.
middleware를 활용하여 route 진입 전에 유저 권한을 확인하는 로직을 추가할 수 있다.
전반적인 로직은 다음과 같다.
- 페이지 진입
- middleware 처리
- 유저의 권한이 없으면, redirect
- 유저의 권한이 있으면, 페이지 로딩
이는 2번의 방법과 유사한데, 2번은 페이지마다 직접 권한 검사하는 function을 추가해야 했다.
하지만, middleware를 활용하면 매번 protected route에 function을 일일이 추가하지 않아도 되는 장점이 있다.
protected route를 지정하고 싶은 page에 대해 패턴을 적어놓으면 middleware가 페이지 진입할 때 유저 권한 function을 실행하기 때문이다.
export async function middleware(req: NextRequest) { const token = req.headers.get('token') // get token from request header const userIsAuthenticated = true // TODO: check if user is authenticated if (!userIsAuthenticated) { const signinUrl = new URL('/signin', req.url) return NextResponse.redirect(signinUrl) } return NextResponse.next() } // Here you can specify all the paths for which this middleware function should run // Supports both a single string value or an array of matchers export const config = { matcher: ['/api/auth/:path*'], }
마치며
나는 위 방안 중 미들웨어를 사용한 4번으로 셋팅하였는데, 방안 2, 3, 4번 중 프로젝트에 상황에 맞게 셋팅하면 좋을 것 같다.
참고로 next.js 공식문서에는 방안 2번에 대한 내용이 담겨있다.미들웨어를 사용한 구체적인 로직은 다음 블로그 포스트(https://uni-s-code.tistory.com/54)를 참조하면 된다.
참고자료
https://shipsaas.com/blog/create-protected-route-nextjs https://dev.to/ariburaco/authentication-with-firebase-in-nextjs-with-ssr-e76
https://nextjs.org/docs/pages/building-your-application/routing/middleware
'Next.js' 카테고리의 다른 글
[Next.js] Next.js 13에서 유저의 권한에 따른 protected route Setting 적용하기 with Middleware (권한에 따른 페이지 접근 제한) (0) 2023.06.20 [Next.js] next.js 에서 meta tag 손쉽게 적용하기 (feat. next-seo) (0) 2023.03.16 [Next] i18n 자동화 프로세스 도입 - (3) : key upload, download (0) 2022.12.11 [Next] production 배포 환경에서, console log 숨기기 (0) 2022.12.04 [Next] i18n 자동화 프로세스 도입 - (1) : i18next-scanner를 통해 코드에서 key 값 추출하기 (1) 2022.11.27