[Next.js] SSR 로그인 유무 확인 with Middleware(v13.4 이상)

Bomin·2024년 2월 4일

[Next.js]

목록 보기
2/2
post-thumbnail

프로덕션 배포를 진행하며 함께 프론트를 진행하던 팀원이 미참여하게 되었다. 그래서 혼자 데모버전의 프로젝트를 리팩토링하고 프로덕션에 맞게 고치기로 했다.
그 과정의 첫 번째로 로그인 로직에 대한 점검 겸 리팩토링을 진행하였는데 아래와 같은 상황이 발생했다.

현재 코드

  • 메인(/) 페이지의 현재 구성
    1. 페이지와 <Header />, <Navbar /> 등 만 정적으로 프리렌더링해서 SSR을 통해 사용자에게 html 먼저 보내주고, 뮤직룸 등 실시간 데이터들을 불러오는 컴포넌트들은 CSR(클라이언트 컴포넌트)로 처리
    2. CSR에는 각각의 스켈레톤 UI를 구현해 사용자의 사용감을 높이고자 함
  • 사용자 인가 인증
    1. axios interceptors -> 원래 코드에서 오류 수정 완료
    2. 모든 요청 시 헤더에 토큰을 담아 보내는 데, 네트워크 요청 시에만 토큰의 유효성 검사 후 유효하지 않은 토큰일 시 토큰이 재발급되고 토큰이 없을 시 로그인 페이지로 redirect된다.

문제 상황

1. 사이트 첫 접속시 홈 화면에서 로그인으로 로딩이 너무 길다.

  • 홈에서 스켈레톤 UI를 보고있는 시간이 너무 길었다.(불필요한 네트워크 요청 발생)
    - 현재 코드에서는 네트워크 요청 후에만 로그인 유무 알 수 있기 때문이다.
  • 또한 배포 사이트에 아래와 같은 오류가 나타나고 페이지가 다운되었다.

    Application error: a client-side exception has occurred (see the browser console for more information).

2. 로그인한 사용자는 로그인과 회원가입 페이지 접근을 막아야 하는데 현재 접근 가능하다.

  • 지금은 그저 네트워크 요청 실패시 로그인 페이지로 리다이렉트되도록 처리되어 있었다. (예시, 홈('/')에서 뮤직룸을 불러오지 못하면 로그인 페이지로 리다이렉트)

즉, 클라이언트에서 로그인 유무 확인 로직이 불분명하다.
이를 해결하기 위해 일단 클라이언트에서의 로그인 유무 분기 처리를 확실히 하도록 고치기로 했다.

해결 과정

그럼 로그인 유무 확인은 어디서 해야할까?

일반 리액트라면 app 컴포넌트에서 useEffect를 이용해 로그인 확인을 통해 접근 분기를 다르게 할 수 있었을 것 같다.

그렇다면 Next.js의 SSR에서는 어떻게 확인해야할까?

여러 방법이 있겠지만 우선 Next.js의 미들웨어를 이용해 페이지마다 사용자의 로그인 유무를 확인하기로 했다.

Middleware

시스템의 일부가 데이터를 통신하고 관리할 수 있도록 하는 모든 소프트웨어(서비스)

  • SSR 웹 프레임워크에서는 DB 접근 등의 작업 처리를 위한 요청 및 응답 처리, 미리 구축된 컴포넌트를 가리키는데 사용되는 경우가 대부분이다.
  • Node.js로 서버를 구성할 때에도 쓰인다. 서버에 들어오는 모든 요청(request)을 검사하고 인증할 수 있기 때문에 서버 보안 부분에 있어서도 효율적인 로직이라고 볼 수 있다.

Next.js의 Middleware

페이지를 렌더링하기 전 서버 측에서 실행되는 함수

  • 주로 request가 서버에서 routing될 때 response를 클라이언트에 보내기 전에 수정하거나 검사하는 데 사용된다.
  • 즉, 특정 요청 전에 무언가를 수행할 수 있게 해주는 기능이다.
  • request가 완료되기 전에 미들웨어 코드가 실행되며 요청에 따라 요청 또는 응답 헤더를 rewrite, redirect, 변환하거나 직접 응답 값을 설정 할 수 있다.
  • 모든 경로에서 실행되는데 특정 경로에서만 실행되도록 커스텀할 수 있다.
export const config = {
  matcher: '/about/:path*',
}

언제 사용될까?

  • 페이지 렌더링 전에 인증을 확인하거나 요청을 확인하고자 할 때
  • 요청 데이터를 사전 처리하거나 특정 API요청을 수행하거나 캐싱할 때
  • 응답 값을 변환하거나 에러를 처리할 때

Middleware로 로그인 유무 확인하기

  • 현재 프로젝트에서는 토큰의 유효성은 쿠키와 axios interceptors를 통해 관리된다.

    accessTokenrefreshToken을 사용한다.
    1. 로그인 시 둘 다 발급 -> 쿠키에 각각 저장
    2. 네트워크 요청 시 헤더에 담아서 보냄
    - 토큰이 유효하지 않은 경우 401 발생 -> axios interceptor에서 재발급 로직 작동
    - 정상적으로 발급 완료 -> 새 토큰 쿠키에 저장
    - 토큰 재발급 오류 시 -> removeCookie 후 로그인 페이지로 리다이렉트

  • 따라서 토큰의 유효성은 axios interceptors에서 진행하고 미들웨어에서는 토큰의 유무에만 유의하며 코드를 짜면된다.
// src/middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';

export function middleware(request: NextRequest) {

	const accessToken = request.cookies.get('accessToken')?.value;
	const pathUrl = request.nextUrl.pathname;

// 로그인 된 사용자가 로그인 페이지 요청 시 / 페이지로 강제 리다이렉트
  if (accessToken && pathUrl.startsWith('/login')) {
		return NextResponse.redirect(new URL('/', request.url));
	}
	
// 로그인 미완료된 사용자가 일반 페이지 요청 시 로그인 페이지로 강제 리다이렉트
if (!accessToken && !pathUrl.startsWith('/login')) {
		return NextResponse.redirect(new URL('/login', request.url));
	}
}

export const config = {
	matcher: [
/*
* 다음 목록들로 시작되는 경로를 미들웨어 체크에서 제외하기:
* - api (API routes)
* - _next/static (static files)
* - _next/image (image optimization files)
* - favicon.ico (favicon file)
* - server/ (네트워크 요청 경로)
*/
		{
		source: '/((?!api|_next/static|_next/image|favicon.ico|server).*)',
		missing: [
			{ type: 'header', key: 'next-router-prefetch' },
			{ type: 'header', key: 'purpose', value: 'prefetch' },
			],
		},
	],
};
  1. 먼저 middleware.ts 파일을 app 폴더와 같은 레벨에 생성한다.
  2. middleware 함수는 request 객체를 매개변수로 갖는데 request 객체에서 쿠키의 토큰값을 가져올 수 있다.
  3. request 객체의 nextUrl의 pathname으로 현재 요청의 경로를 가져온다.
  4. 조건문과 NextResponse.redirect(경로)로 분기 처리를 해준다.
    1. accessToken이 존재하고, pathUrl'/login'로 시작한다면, 로그인된 사용자가 로그인 페이지를 요청한 경우 -> 메인 페이지로 이동
    2. accessToken이 존재하지 않고, pathUrl이 '/login'으로 시작하지 않는다면, 로그인이 완료되지 않은 사용자가 일반 페이지를 요청한 경우 -> 로그인 페이지로 이동
  5. 이대로 끝내면 불필요한 모든 경로에 미들웨어가 실행되므로 제외 규칙을 설정한다.
    1. config 객체안의 matcher를 통해 요청 경로에 대한 매칭 규칙을 설정할 수 있다.
    2. API 경로(/api로 시작하는 경로), 정적 파일(/_next/static로 시작하는 경로), 이미지 최적화 파일(/_next/image로 시작하는 경로), 파비콘 파일(favicon.ico 경로), 프록시 설정한 네트워크 요청 URL(server/로 시작하는 경로)를 제외시켜준다.
    3. 또한 미들웨어를 거칠 필요없는 prefetch 관련 요청들도 설정해준다.

결과

마무리

Next.js 미들웨어를 처음으로 프로젝트에 사용해보며 사용법을 익히고 원리를 이해할 수 있어서 좋은 경험이었다.

코드 리팩토링과 분석 과정에서 원래의 코드와 로직을 해석하고 문제를 찾는 것이 예상보다 어려웠지만, 이러한 과정을 통해 많은 경험을 쌓을 수 있었던것 같다. 또한, 타인이 작성한 코드를 분석하면서 주석을 잘 달고 유지보수가 쉽게 코드를 작성하는 것의 중요성을 다시 한 번 깨닫게 되었다.

참고자료
블로그
공식문서

profile
Frontend-developer

0개의 댓글