-
[Next.js] Next.js 13에서 유저의 권한에 따른 protected route Setting 적용하기 with Middleware (권한에 따른 페이지 접근 제한)Next.js 2023. 6. 20. 19:39
들어가며
지난 번 글([Next.js] Next.js 13에서 유저의 권한에 따른 protected route / protected api Setting (권한에 따른 페이지 접근 제한)에서 권한에 따라 페이지 접근 로직을 다루었다.
이 글에 나온 내용에서 방안4인 middleware를 사용한 방법으로 실제 프로젝트에 도입해보려고 한다.
next.js에서 middleware는 페이지를 이동하려고 할 때, 중간에 검증 함수 역할을 한다.
여기서 유저의 권한 인증에 대해 검증 함수를 추가하여, 유저 권한에 따른 페이지 접근 가능 여부를 구현가능하다.
예를 들어, 로그인한 유저가 로그인 페이지에 접근하는 것은 올바르지않다. 반대로, 로그인 하지 않은 유저가 로그인이 필요한 마이페이지에 접근하는 것도 옳지 않다. 이때, 특정 권한이 필요한 페이지에 접근하면 특정 페이지로 redirect를 하는 로직을 middleware를 이용하여 구현해보자.
참고로, 여기서 유저 인증 검사에 대해서는 Firebase/Authentication을 사용했다.
1. middleware 셋팅
먼저, root 위치에 middleware.ts 파일을 생성한다.
middleware를 실행 하기 위해서는 matcher config와 middleware 함수 안에서 조건문을 사용한다.
1 ) matcher config
middleware를 실행하고 싶은 url을 기입한다. 이때, 정규식도 사용이 가능하나 여기서는 편의를 위해 2개의 url을 기입했다.
- 'accounts/login' -> 로그인 페이지. 즉, 로그인이 안된 유저만 접근 가능
- 'users/profile' -> 프로파일 페이지. 즉 로그인이 완료된 유저만 접근 가능
export const config = { matcher: ["/accounts/login", "/users/profile"], };
2 ) middleware안의 조건문
url을 보면 알듯이, ‘accounts/login’ 같은 경우 로그인을 하지 않은 유저만 접근이 가능하고, ‘users/profile’은 로그인을 한 유저만 접근이 가능하다.
middleware안에 각 경우에 따라 분기처리를 통해 로직을 구분한다.
* withoutAuth, withAuth는 권한 검사를 하여 redirect할지, 통과할지에 대한 로직이 담긴 function (밑에서 구현)
export function middleware(request: NextRequest) { if (request.nextUrl.pathname.startsWith("/accounts/login")) { console.log("middleware: withoutAuth"); return withoutAuth(request); } if (request.nextUrl.pathname.startsWith("/users/profile")) { console.log("middleware: withAuth"); return withAuth(request); } }
정리하면 다음과 같다.
import type { NextRequest } from "next/server"; import { withoutAuth } from "./middlewares/withoutAuth"; import { withAuth } from "./middlewares/withAuth"; // This function can be marked `async` if using `await` inside export async function middleware(request: NextRequest) { if (request.nextUrl.pathname.startsWith("/accounts/login")) { console.log("middleware"); return await withoutAuth(request); } if (request.nextUrl.pathname.startsWith("/users/profile")) { console.log("middleware"); return await withAuth(request); } } // See "Matching Paths" below to learn more export const config = { matcher: ["/accounts/login", "/users/profile"], };
첫번째 조건문에서는 유저권한이 없는지 체크하는 withoutAuth 함수가,
두번째 조건문에서는 유저권한이 있는지 체크하는 withAuth 함수가 필요하다.
아래에서 바로 살펴보자.
2. 유저의 권한 검사하는 로직 구현
1 ) 유저의 권한이 필요한 경우 (withAuth 함수)
대략적인 로직은 다음과 같다.
(1) token 가져오기 (여기에서는 cookie에서 가져왔다)
(2) token을 통해 유효한 토큰인지 확인 ( ex. api routes 기능 활용 )
(3) 유효하면 페이지 통과
(4) 유효하지 않다면 redirect// middlewares/withAuth import { NextResponse } from "next/server"; export async function withAuth(req, redirect = null) { let redirectUrl = new URL("/accounts/login", req.url); if (redirect) { redirectUrl = new URL(redirect, req.url); } // token 가져오기 const token = req.cookies.get("token")?.value; if (!token) { return NextResponse.redirect(redirectUrl); } // token을 통해 유효한 토큰인지 확인 let decodedToken; try { decodedToken = await fetch(`${process.env.DOMAIN_URL}/api/auth`, { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ token: token, }), }); // 유효하지 않다면 redirect if (!decodedToken) return NextResponse.redirect(redirectUrl); } catch (e) { console.log(e); return NextResponse.redirect(redirectUrl); } // 유효하면 페이지 통과 return NextResponse.next(); }
// pages/api/auth import checkSupportMethod from "@/firebase/error/check_method"; import { NextApiRequest, NextApiResponse } from "next"; import handlerError from "@/firebase/error/handle_error"; import FirebaseAdmin from "@/firebase/config/firebase_admin"; import { UnauthorizedError } from "@/firebase/error/error"; export default async function Handler( req: NextApiRequest, res: NextApiResponse ) { const supportMethod = ["POST"]; try { checkSupportMethod(supportMethod, req.method); const decodedToken = await FirebaseAdmin.getInstance().Auth.verifyIdToken( req.body.token ); if (decodedToken && decodedToken.uid) { res.status(200).json(decodedToken.uid); } else { const error = new UnauthorizedError(); handlerError(error, res); } } catch (err) { handlerError(err, res); } }
2 ) 유저의 권한이 없어야 하는 경우(미로그인 상태)
대략적인 로직은 다음과 같다.
(1) token 가져오기 (여기에서는 cookie에서 가져왔다)
(2) token을 통해 유효한 토큰인지 확인
(3) 유효하지 않으면 페이지 통과
(4) 유효하면 redirect// middlewares/withoutAuth import { NextResponse } from "next/server"; export async function withoutAuth(req, redirect = null) { let redirectUrl = new URL("/", req.url); if (redirect) { redirectUrl = new URL(redirect, req.url); } // token 가져오기 const token = req.cookies.get("token")?.value; if (!token) { return NextResponse.next(); } // token을 통해 유효한 토큰인지 확인 let decodedToken; try { decodedToken = await fetch(`${process.env.DOMAIN_URL}/api/auth`, { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ token: token, }), }); // 유효하면 redirect if (decodedToken) { return NextResponse.redirect(redirectUrl); } } catch (e) { console.log(e); return NextResponse.redirect(redirectUrl); } // 유효하지 않으면 페이지 통과 return NextResponse.next(); }
마치며
middleware를 통해 권한 검사를 진행하면서, 페이지에 직접 url 접근을 편리하게 막을 수 있는 것 같다.
'Next.js' 카테고리의 다른 글
[Next.js] Next.js 13에서 유저의 권한에 따른 protected route / protected api Setting (권한에 따른 페이지 접근 제한) (1) 2023.06.02 [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