Published on

OAuth redirect_uri mismatch 즉시 해결 체크리스트

Authors

서드파티 로그인(OAuth/OIDC)을 붙이다 보면 거의 반드시 한 번은 만나게 되는 에러가 redirect_uri mismatch입니다. 겉으로는 “리다이렉트 URI가 다르다”는 단순한 메시지지만, 실제 원인은 문자 하나 차이, 인코딩 방식, 프록시/Ingress가 바꿔치기한 스킴/호스트, 환경별 콜백 URL 혼선 등 다양합니다.

이 글은 “지금 당장 로그인 막혔다”는 상황에서, 가장 빠르게 원인을 좁히고 복구하기 위한 즉시 해결 체크리스트로 구성했습니다.

1) 먼저 원리를 30초만 정리: 무엇이 ‘mismatch’인가

OAuth 2.0 Authorization Code Flow(또는 OIDC)에서 인증 서버는 보통 다음을 비교합니다.

  • 클라이언트 등록 정보에 저장된 redirect URI 목록
  • /authorize 요청에 포함된 redirect_uri 파라미터 값

이 둘이 정확히 일치(exact match) 하지 않으면 거절합니다. “대충 비슷하면 통과”가 아니라, 대부분의 공급자는 문자 단위로 비교합니다.

따라서 해결의 핵심은 하나입니다.

> 실제로 인증 서버로 날아간 redirect_uri가 정확히 무엇인지 캡처하고, 공급자 콘솔에 등록된 값과 1:1로 맞춘다.

2) 즉시 해결 1단계: 실제 authorize 요청의 redirect_uri를 캡처

가장 먼저 해야 할 일은 추측을 멈추고 증거를 확보하는 것입니다.

브라우저에서 확인

  • 개발자도구(Network) → authorize 또는 oauth 요청 선택
  • Query String Parameters에서 redirect_uri 값을 복사

서버 로그에서 확인(백엔드가 authorize URL 생성 시)

예: Node/Express에서 authorize URL을 만들 때

const authorizeUrl = new URL("https://accounts.example.com/oauth2/v2/auth");
authorizeUrl.searchParams.set("client_id", process.env.CLIENT_ID);
authorizeUrl.searchParams.set("redirect_uri", process.env.REDIRECT_URI);
authorizeUrl.searchParams.set("response_type", "code");
authorizeUrl.searchParams.set("scope", "openid email profile");

console.log("[oauth] authorizeUrl=", authorizeUrl.toString());

이 출력 문자열에 포함된 redirect_uri실제 비교 대상입니다.

3) 즉시 해결 2단계: ‘정확히 일치’ 체크리스트(가장 흔한 12가지)

아래 항목은 실제 장애에서 빈도 높은 순서로 정리했습니다. 위에서 캡처한 redirect_uri와 공급자 콘솔 등록 값을 문자열로 나란히 놓고 체크하세요.

3.1 스킴(http/https) 불일치

  • 로컬은 http://localhost:3000/callback
  • 운영은 https://app.example.com/callback

프록시 뒤에서 앱이 http로 인식해 redirect_uri를 http로 만들면 mismatch가 납니다.

3.2 호스트 불일치(www 유무, 서브도메인)

  • https://example.com/callback
  • https://www.example.com/callback
  • https://app.example.com/callback

공급자 콘솔에는 보통 정확한 호스트를 등록해야 합니다.

3.3 포트 불일치(특히 localhost)

  • http://localhost:3000/callback vs http://localhost:5173/callback

Vite/Next/CRA 등 개발 서버 포트가 바뀌면 바로 터집니다.

3.4 경로(path) 불일치(슬래시 하나)

  • /oauth/callback vs /oauth/callback/

트레일링 슬래시 유무는 별개 URI로 취급됩니다.

3.5 대소문자 불일치

일부 공급자/환경에서는 path의 대소문자도 그대로 비교됩니다.

  • /OAuth/Callback vs /oauth/callback

3.6 URL 인코딩 차이(가장 헷갈리는 지점)

redirect_uri 자체는 URL이므로, authorize 요청에서는 인코딩되어 전달됩니다.

  • 올바른 예: redirect_uri=https%3A%2F%2Fapp.example.com%2Foauth%2Fcallback

문제는 이중 인코딩(double encoding) 입니다.

  • https%253A%252F%252F... 처럼 %25가 보이면 의심하세요.

이중 인코딩이 생기는 전형적 패턴

  • 이미 인코딩된 redirect_uri를 또 encodeURIComponent로 감쌈
// ❌ 잘못된 예: redirectUri가 이미 인코딩된 문자열인데 또 인코딩
const redirectUri = encodeURIComponent(process.env.REDIRECT_URI);
authorizeUrl.searchParams.set("redirect_uri", encodeURIComponent(redirectUri));

// ✅ 올바른 예: 원본 URI를 그대로 넣고, URLSearchParams가 인코딩을 맡게
authorizeUrl.searchParams.set("redirect_uri", process.env.REDIRECT_URI);

3.7 쿼리스트링 포함 여부

공급자에 따라 쿼리스트링이 포함된 redirect_uri 등록을 허용/비허용하거나, 등록한 쿼리까지 exact match를 요구합니다.

  • 등록: https://app.example.com/callback
  • 요청: https://app.example.com/callback?source=google

이 경우 mismatch가 날 수 있습니다. 가능하면 redirect_uri는 경로까지만 고정하고, 부가 정보는 state에 넣는 편이 안전합니다.

3.8 프래그먼트(#) 사용

https://app.example.com/callback#section 같은 프래그먼트는 보통 redirect_uri로 권장되지 않으며, 공급자가 거부할 수 있습니다.

3.9 환경변수/설정 파일이 다른 값을 가리킴

  • 프론트엔드 .env와 백엔드 .env가 서로 다른 도메인을 사용
  • staging/production이 뒤바뀜

운영 배포에서 흔한 원인입니다. 배포 산출물에 실제로 들어간 값(빌드된 JS/컨테이너 env)을 확인하세요.

3.10 여러 콜백 URL 중 “사용 중인 것”이 등록되지 않음

공급자 콘솔에 여러 redirect URI를 등록할 수 있어도, 실제 요청에 쓰는 값이 목록에 없으면 실패합니다.

  • 예: https://api.example.com/auth/callback을 쓰는데 콘솔에는 https://app.example.com/auth/callback만 등록

3.11 reverse proxy/Ingress가 host, scheme을 바꿔치기

Kubernetes Ingress, ALB/NGINX, Cloudflare, API Gateway 뒤에서 앱이 외부 URL을 잘못 추정하면 redirect_uri가 틀어집니다.

  • 외부: https://app.example.com
  • 내부 앱이 인식: http://app:8080

이런 경우는 X-Forwarded-Proto, X-Forwarded-Host를 신뢰하도록 프레임워크 설정이 필요합니다.

Ingress/프록시 쪽에서 400/413 같은 다른 HTTP 문제도 같이 터지면, 프록시 설정 점검이 함께 필요할 수 있습니다. 관련해서는 EKS NGINX Ingress 400·413 해결 - body·버퍼 튜닝도 같이 참고하면 전체 네트워크 경로를 점검하는 데 도움이 됩니다.

3.12 공급자 정책: 와일드카드/부분 일치 불가

  • https://*.example.com/callback 같은 와일드카드 등록이 안 되는 공급자가 많습니다.
  • “도메인만 맞으면”이 아니라 “전체 URI가 완전 동일”이어야 합니다.

4) 프레임워크/런타임별 빠른 처방

여기부터는 “왜 내 앱은 외부 https인데 redirect_uri가 http로 만들어지지?” 같은 케이스를 빠르게 고치는 팁입니다.

4.1 Express(behind proxy)에서 scheme/host 바로잡기

Express는 프록시 뒤에서 원래 스킴을 알기 위해 trust proxy 설정이 필요합니다.

import express from "express";

const app = express();
app.set("trust proxy", true);

app.get("/debug/url", (req, res) => {
  res.json({
    protocol: req.protocol,
    host: req.get("host"),
    originalUrl: req.originalUrl,
    xfp: req.get("x-forwarded-proto"),
    xfh: req.get("x-forwarded-host"),
  });
});

req.protocolhttp로 찍히면 redirect_uri 생성 로직이 틀어질 확률이 큽니다.

4.2 Spring Boot(Forwarded 헤더 처리)

Spring Boot는 프록시 환경에서 Forwarded/X-Forwarded-* 헤더 처리가 중요합니다.

server:
  forward-headers-strategy: framework

환경에 따라 native가 필요한 경우도 있습니다. 핵심은 외부에서 들어온 스킴/호스트를 애플리케이션이 올바르게 재구성하도록 만드는 것입니다.

4.3 Next.js / 프론트엔드에서 리다이렉트 URI 고정

프론트에서 현재 location으로 redirect_uri를 동적으로 만들면, staging 도메인/프리뷰 URL 등에서 값이 흔들립니다. 가능하면 환경변수로 고정하세요.

// ✅ redirect_uri는 빌드/배포 환경변수로 고정
const redirectUri = process.env.NEXT_PUBLIC_OAUTH_REDIRECT_URI!;

const url = new URL("https://accounts.example.com/oauth2/auth");
url.searchParams.set("client_id", process.env.NEXT_PUBLIC_CLIENT_ID!);
url.searchParams.set("redirect_uri", redirectUri);
url.searchParams.set("response_type", "code");

이미지 최적화처럼 “환경에 따라 외부 도메인이 달라져서” 설정이 꼬이는 문제는 프론트에서 자주 발생합니다. 비슷한 결의 환경/도메인 설정 이슈는 Next.js 이미지 최적화 실패? remotePatterns·403 해결도 참고할 만합니다.

5) 운영에서 재발 방지: 체크 자동화(테스트/헬스엔드포인트)

redirect_uri mismatch는 배포 후에야 발견되면 치명적입니다. 아래처럼 “현재 서버가 생각하는 외부 Base URL”을 점검하는 엔드포인트를 임시로라도 두면 좋습니다.

5.1 현재 인식한 외부 URL을 출력하는 /health/oauth

app.get("/health/oauth", (req, res) => {
  const baseUrl = `${req.protocol}://${req.get("host")}`;
  const computedRedirect = `${baseUrl}/oauth/callback`;

  res.json({
    baseUrl,
    computedRedirect,
    envRedirect: process.env.REDIRECT_URI,
    xForwardedProto: req.get("x-forwarded-proto"),
    xForwardedHost: req.get("x-forwarded-host"),
  });
});
  • computedRedirectenvRedirect가 다르면 원인 추적이 매우 쉬워집니다.
  • 보안상 운영에 영구 노출은 피하고, IP 제한/일시적 활성화를 권장합니다.

6) 그래도 안 되면: “공급자 측 로그/정책” 확인 포인트

모든 걸 맞췄는데도 계속 mismatch가 난다면 다음을 확인하세요.

  • 해당 OAuth 앱(클라이언트)의 환경이 맞는지: prod 앱과 staging 앱이 따로 있는 공급자가 많습니다.
  • 승인된 redirect URI 변경이 저장/배포되었는지: 콘솔 저장 후 반영 지연이 있는 경우가 있습니다.
  • OIDC의 경우 issuer/authorize endpoint가 맞는지: 다른 테넌트/리전으로 요청하면 등록 정보가 다릅니다.

인증/토큰 흐름에서 “요청 스키마가 다르다”는 류의 에러는 다른 API에서도 자주 보입니다. 원인-재현-검증 순서로 좁히는 디버깅 방식은 OpenAI Responses API 422 스키마 검증 에러 해결 가이드와 같은 접근으로 생각하면 빠릅니다.

7) 최종 점검용 ‘즉시 해결’ 한 장 요약

배포/장애 상황에서 아래 순서대로만 하면 대부분 10분 내에 끝납니다.

  1. 브라우저 Network 또는 서버 로그로 실제 authorize 요청의 redirect_uri 원문을 확보
  2. 공급자 콘솔의 등록 redirect URI와 문자열 완전 일치 비교
  3. 특히 다음 6개를 우선 확인
    • http vs https
    • www/서브도메인
    • 포트
    • 트레일링 슬래시
    • 인코딩(이중 인코딩 %253A 등)
    • 프록시 헤더(X-Forwarded-Proto/Host)로 인한 오인식
  4. 프록시 뒤라면 프레임워크의 forwarded header 신뢰 설정 적용(Express trust proxy, Spring forward-headers-strategy 등)
  5. redirect_uri는 가능하면 환경변수로 고정하고, 동적 생성은 최소화

redirect_uri mismatch는 “설정 실수”라기보다 “요청이 만들어지는 경로가 복잡해진 결과”인 경우가 많습니다. 위 체크리스트대로 실제 요청을 캡처 → exact match로 정렬 → 프록시/인코딩을 교정하는 흐름을 습관화하면, 같은 유형의 장애를 반복해서 겪지 않게 됩니다.