Next.js 인증 리다이렉트 루프 방지 플레이북: 로그인 UX와 보안을 동시에 지키는 운영 설계
💡 Key Takeaways
- 리다이렉트 루프는 코드 한 줄 문제가 아니라 경로 설계와 세션 판정 책임이 섞일 때 발생합니다.
- 미들웨어는 차단만 담당하고 세션 복구는 라우트 핸들러로 분리해야 루프 가능성을 낮출 수 있습니다.
- returnUrl, 공개 경로, 보호 경로를 명시적으로 분류하면 예외 규칙 누락을 줄일 수 있습니다.
- 배포 후에는 302 비율보다 동일 요청의 연속 리다이렉트 횟수를 우선 모니터링해야 합니다.
Next.js 인증 리다이렉트 루프 방지 플레이북: 로그인 UX와 보안을 동시에 지키는 운영 설계
이 글 주제를 처음 정리하게 된 계기가 있습니다. 배포 직후 고객센터로 “로그인 버튼 누르면 화면이 계속 튄다”는 문의가 몰렸고, 내부에서는 토큰 만료나 쿠키 도메인부터 의심했습니다. 그런데 HAR를 열어보니 답은 단순했습니다. /dashboard로 들어가려는 요청이 /login으로 갔다가 다시 /dashboard로 튕기고, 302가 짧은 시간에 반복됐습니다. 코드가 크게 망가진 게 아니라 경계가 흐린 상태였던 겁니다. 로그인 상태 판정을 미들웨어와 페이지 레벨 훅이 동시에 하면서 서로의 결정을 뒤집고 있었습니다. 이때 느낀 건, 인증 이슈는 “보안 규칙 추가”보다 “책임 분리”를 먼저 해야 풀린다는 점이었습니다.
저희가 정착한 기준은 경로를 세 덩어리로 고정하는 방식입니다. 공개 경로는 누구든 통과, 비인증 전용 경로는 로그인 사용자가 오면 홈으로 1회 이동, 보호 경로는 미인증 시 로그인으로 보내되 returnUrl 규칙을 하나로 통일합니다. 중요한 건 한 요청에 리다이렉트 한 번만 허용하는 원칙입니다. 미들웨어 안에서 세션 재발급까지 처리하려고 하면, 네트워크가 조금만 흔들려도 분기가 꼬이고 루프가 다시 생깁니다. 그래서 미들웨어는 통과/차단만, 세션 복구는 라우트 핸들러에서 처리하도록 분리했습니다. 말로 들으면 사소해 보이지만, 이 경계를 정한 뒤부터 “가끔만 재현되는 로그인 이슈”가 눈에 띄게 줄었습니다.
배포 전에는 아래 다섯 가지를 고정 점검합니다. matcher를 줄여 정적 리소스와 웹훅은 인증 검사에서 빼고, 미들웨어에서는 요청 경로와 쿠키 존재만 읽고 즉시 분기합니다. 리다이렉트 시에는 redirectReason을 남겨 나중에 로그와 연결합니다. 로그인 완료 핸들러에서는 returnUrl을 허용 목록으로만 통과시킵니다. 마지막으로 E2E에 세 가지 케이스를 고정합니다. “비인증 사용자가 보호 경로 접근”, “인증 사용자가 로그인 경로 접근”, “만료 직전 세션 갱신”입니다. 팀 내 합의 포인트는 하나였습니다. 인증 규칙을 페이지마다 덧붙이지 말고 정책 파일 한 곳에서만 바꾸자. 이걸 지키면 회귀 버그를 잡는 속도가 확실히 빨라집니다.
운영 단계에서는 에러율보다 루프 신호를 먼저 봅니다. 저희는 302 후 동일 path 재요청 횟수, 세션 만료 직후 로그인 재진입 비율, returnUrl 검증 실패 건수를 대시보드 맨 위에 둡니다. 알림도 “5xx 증가” 같은 넓은 조건보다 “동일 사용자 10초 내 302 세 번 이상” 같은 패턴 조건이 실전에서 잘 맞았습니다. 장애 중 커뮤니케이션도 미리 준비해 두면 효과가 큽니다. “현재 세션 동기화 지연으로 재로그인이 필요합니다”처럼 사용자 행동이 분명한 문구를 먼저 내보내면 이탈이 줄어듭니다.
마지막으로 자주 놓치는 함정이 하나 있습니다. 급히 고치겠다고 임시 예외 경로를 추가한 뒤 제거하지 않는 경우입니다. 이게 다음 배포 때 다시 루프의 씨앗이 됩니다. 그래서 수정 후 24시간 안에 “예외 규칙 정리 PR”을 분리해 반드시 마무리합니다. 인증 미들웨어는 접근 차단 도구가 아니라, 실패 상황에서도 사용자가 다음 행동을 선택할 수 있게 흐름을 유지하는 장치라고 보는 편이 운영에 훨씬 도움이 됩니다.