- Published on
OAuth redirect_uri mismatch 즉시 해결 체크리스트
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서드파티 로그인(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/callbackhttps://www.example.com/callbackhttps://app.example.com/callback
공급자 콘솔에는 보통 정확한 호스트를 등록해야 합니다.
3.3 포트 불일치(특히 localhost)
http://localhost:3000/callbackvshttp://localhost:5173/callback
Vite/Next/CRA 등 개발 서버 포트가 바뀌면 바로 터집니다.
3.4 경로(path) 불일치(슬래시 하나)
/oauth/callbackvs/oauth/callback/
트레일링 슬래시 유무는 별개 URI로 취급됩니다.
3.5 대소문자 불일치
일부 공급자/환경에서는 path의 대소문자도 그대로 비교됩니다.
/OAuth/Callbackvs/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.protocol이 http로 찍히면 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"),
});
});
computedRedirect와envRedirect가 다르면 원인 추적이 매우 쉬워집니다.- 보안상 운영에 영구 노출은 피하고, IP 제한/일시적 활성화를 권장합니다.
6) 그래도 안 되면: “공급자 측 로그/정책” 확인 포인트
모든 걸 맞췄는데도 계속 mismatch가 난다면 다음을 확인하세요.
- 해당 OAuth 앱(클라이언트)의 환경이 맞는지: prod 앱과 staging 앱이 따로 있는 공급자가 많습니다.
- 승인된 redirect URI 변경이 저장/배포되었는지: 콘솔 저장 후 반영 지연이 있는 경우가 있습니다.
- OIDC의 경우 issuer/authorize endpoint가 맞는지: 다른 테넌트/리전으로 요청하면 등록 정보가 다릅니다.
인증/토큰 흐름에서 “요청 스키마가 다르다”는 류의 에러는 다른 API에서도 자주 보입니다. 원인-재현-검증 순서로 좁히는 디버깅 방식은 OpenAI Responses API 422 스키마 검증 에러 해결 가이드와 같은 접근으로 생각하면 빠릅니다.
7) 최종 점검용 ‘즉시 해결’ 한 장 요약
배포/장애 상황에서 아래 순서대로만 하면 대부분 10분 내에 끝납니다.
- 브라우저 Network 또는 서버 로그로 실제 authorize 요청의 redirect_uri 원문을 확보
- 공급자 콘솔의 등록 redirect URI와 문자열 완전 일치 비교
- 특히 다음 6개를 우선 확인
httpvshttpswww/서브도메인- 포트
- 트레일링 슬래시
- 인코딩(이중 인코딩
%253A등) - 프록시 헤더(
X-Forwarded-Proto/Host)로 인한 오인식
- 프록시 뒤라면 프레임워크의 forwarded header 신뢰 설정 적용(Express
trust proxy, Springforward-headers-strategy등) - redirect_uri는 가능하면 환경변수로 고정하고, 동적 생성은 최소화
redirect_uri mismatch는 “설정 실수”라기보다 “요청이 만들어지는 경로가 복잡해진 결과”인 경우가 많습니다. 위 체크리스트대로 실제 요청을 캡처 → exact match로 정렬 → 프록시/인코딩을 교정하는 흐름을 습관화하면, 같은 유형의 장애를 반복해서 겪지 않게 됩니다.