Skip to content

ADR-25: OAuth 리턴 URL 처리 ​

🇺🇸 English Version

날짜작성자리포지토리
2026-01-16@specvitalweb

컨텍스트 ​

홈페이지 외의 페이지에서 GitHub OAuth 로그인 시 원래 페이지가 아닌 대시보드로 리다이렉트되는 문제 발생.

진입 페이지기대 동작실제 동작
/ko/pricing/ko/pricing으로 복귀/ko/dashboard로 리다이렉트
/ko/explore/ko/explore로 복귀/ko/dashboard로 리다이렉트
/ko/{owner}/{repo}분석 페이지로 복귀/ko/dashboard로 리다이렉트

근본 원인 ​

1. 리다이렉트 로직 충돌

홈페이지의 AuthenticatedRedirect 컴포넌트가 전역 리다이렉트 동작에 영향.

2. 클라이언트-서버 스토리지 불일치

Client (브라우저)                   Server (Route Handler)
┌─────────────────┐                ┌──────────────────────┐
│ sessionStorage  │ ─────────X──── │ OAuth callback       │
│ (returnTo URL)  │  접근 불가    │ (code exchange)      │
└─────────────────┘                └──────────────────────┘

초기 구현은 sessionStorage에 returnTo 저장, 하지만 Next.js App Router OAuth 콜백은 서버 사이드 Route Handler로 실행되어 브라우저 sessionStorage 접근 불가.

제약 조건 ​

제약 조건출처영향
서버 사이드 OAuth 콜백Next.js App Router클라이언트 전용 스토리지 사용 불가
로케일 보존ADR-17리턴 URL에 로케일 접두사 포함 필수
보안 요구사항OAuth 모범 사례오픈 리다이렉트 공격 방지 필수

결정 ​

쿠키 기반 리턴 URL 저장 및 서버 사이드 리다이렉트 검증 채택.

typescript
// OAuth 리다이렉트 전 (클라이언트 사이드)
const returnTo = window.location.pathname;
document.cookie = `returnTo=${encodeURIComponent(returnTo)}; path=/; max-age=300; SameSite=Lax`;
typescript
// OAuth 콜백 (서버 사이드 Route Handler)
const returnTo = cookies().get("returnTo")?.value;
const safeReturnTo = validateReturnUrl(returnTo);
cookies().delete("returnTo");
redirect(safeReturnTo || "/dashboard");

쿠키 설정 ​

파라미터값근거
저장소HTTP Cookie서버 사이드 접근 가능
Max-Age300초 (5분)OAuth 플로우 커버, 오래된 URL 위험 제한
SameSiteLax크로스 사이트 요청 공격 방지
Path/모든 라우트에서 사용 가능

검토한 옵션 ​

옵션 A: sessionStorage 기반 (초기, 실패) ​

  • 브라우저 sessionStorage에 returnTo 저장

장점:

  • 단순함, 쿠키 관리 불필요
  • 브라우저 탭 단위 스코프

단점:

  • 서버 사이드 Route Handler에서 sessionStorage 접근 불가
  • Next.js App Router와 아키텍처적 비호환

결정: 기각.

옵션 B: 쿠키 기반 저장 (채택) ​

  • OAuth 리다이렉트 전 HTTP 쿠키에 returnTo 저장
  • 콜백에서 서버가 쿠키 읽기, 검증, 삭제

장점:

  • 서버 사이드 Route Handler와 호환
  • 단기 수명 (5분)
  • SameSite=Lax로 CSRF 보호

단점:

  • 쿠키 관리 필요
  • 오픈 리다이렉트 공격 벡터 (검증으로 완화)

결정: 채택.

옵션 C: OAuth 상태 파라미터 ​

  • OAuth 상태 파라미터에 returnTo 인코딩

장점:

  • 무상태, OAuth 스펙 내장

단점:

  • GitHub 상태 파라미터 크기 제한
  • CSRF 보호용으로 이미 사용 시 복잡도 증가

결정: 기각.

옵션 D: 데이터베이스 세션 저장 ​

  • 세션 테이블에 returnTo 저장

장점:

  • 안정적인 서버 사이드 저장

단점:

  • 데이터베이스 지연 추가
  • 리다이렉트 URL 저장에 과도한 엔지니어링
  • PaaS 우선 단순성 위반 (ADR-06)

결정: 기각.

구현 ​

리턴 URL 플로우 ​

1. 사용자가 /ko/pricing에서 "GitHub으로 로그인" 클릭
   └─> 쿠키 설정: returnTo=/ko/pricing; max-age=300

2. GitHub OAuth로 리다이렉트
   └─> 사용자가 github.com에서 인증

3. GitHub이 /api/auth/callback/github으로 리다이렉트
   └─> 서버가 returnTo 쿠키 읽기
   └─> 검증: "/"로 시작하고 "//"가 아님
   └─> 쿠키 삭제
   └─> /ko/pricing으로 리다이렉트

4. 사용자가 /ko/pricing에 복귀 (인증됨)

URL 검증 로직 ​

typescript
function validateReturnUrl(url: string | undefined): string | null {
  if (!url) return null;

  // 단일 슬래시로 시작 필수 (상대 경로)
  if (!url.startsWith("/")) return null;

  // 프로토콜 상대 URL 거부 (오픈 리다이렉트 벡터)
  if (url.startsWith("//")) return null;

  // 임베디드 자격 증명 URL 거부
  if (url.includes("@") || url.includes("\\")) return null;

  return url;
}

결과 ​

긍정적:

  • OAuth 로그인 후 원래 페이지로 복귀
  • 로케일/i18n 컨텍스트 보존
  • Next.js App Router 서버 사이드 핸들러와 호환
  • 짧은 쿠키 만료 (5분)로 공격 윈도우 제한

부정적:

  • 쿠키 오버헤드 (최소, <100 바이트)
  • 오픈 리다이렉트 공격 표면 (검증으로 완화)
  • 쿠키 비활성화 시 대시보드로 폴백

보안 고려사항 ​

오픈 리다이렉트 방지 ​

공격 벡터완화 방법
절대 URL 삽입/ 접두사 필수
프로토콜 상대 URL// 접두사 거부
자격 증명 포함 URL@ 문자 거부
백슬래시 우회\ 문자 거부

쿠키 보안 속성 ​

속성값이점
SameSiteLax크로스 사이트 공격 방지
Max-Age300오래된 URL 위험 제한
Path/애플리케이션 스코프

참조 ​

Open-source test coverage insights