Published:
Updated:

FoodyMoody 프로젝트에 대한 설명입니다.

문제 발생

이 문제는 서버 재운영 후 가장 먼저 만나게 된 에러였다. 하지만, 대부분의 블로그들에서는 백엔드의 잘못, 즉 client_id 혹은 client_secret란을 yml 파일에 잘못 올렸던 것이 원인이었다.

하지만, 아무리 봐도 client_id와 client_secret은 틀린 것이 없었고, redirect_url마저 교차 검증을 진행했음에도 문제가 없었다. OAuth 동의 화면에서도 이미 프로덕션 단계로 넘어가 있는 상태였고, 도저히 원인을 모르겠어서 일단 다른 문제를 해결하고 있었다.

몇 주 동안 끙끙 앓으며 대체 왜 이런 문제가 발생하는 걸까를 생각해왔지만, 도저히 알 길이 없었다. 왜냐하면 백엔드의 테스트 코드에서는 아주 잘 작동되고, 프론트로 넘어가는 데에 아무 문제가 없었기 때문이다.

문제 해결

결국 원인은 프론트 코드에 있었다. 아무리 검색을 해 봐도 프론트 코드의 오류로 인해 발생한 포스트가 없었기 때문에 더 오래 걸렸던 것 같다. 이런 걸 보면 결국 오류를 해결해주는 건 검색이 아닌 나 자신의 사고인 것 같다. 프론트쪽의 문제가 아닐까하고 계속 의문을 품어왔지만, 리액트 코드를 봐도 전혀 모르겠기도 했고 보통 백엔드의 문제로 401 에러가 발생한다는 사고 방식으로 생각했던 게 문제의 원인이었다.

이전 코드

import { styled } from "styled-components";
import { GoogleIcon } from "../icon/icons";
import { PATH } from "constants/path";

const { MODE, VITE_GOOGLE_CLIENT_ID } = import.meta.env;

export const OAuthButton = () => {
  const isDev = MODE === "development";
  console.log("isDev", isDev);

  const LOCAL_URL = "http://localhost:5173";
  const GOOGLE_URL = `https://accounts.google.com/o/oauth2/v2/auth?scope=https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/userinfo.email&client_id=${VITE_GOOGLE_CLIENT_ID}&response_type=code&redirect_uri=${
    isDev ? LOCAL_URL + PATH.GOOGLE : "https://foodymoody.site" + PATH.GOOGLE
  }&access_type=offline`;
  // const GOOGLE_URL = `https://accounts.google.com/o/oauth2/v2/auth?
  // 	client_id=${VITE_GOOGLE_CLIENT_ID}
  // 	&redirect_uri=${isDev ? LOCAL_URL + PATH.GOOGLE : VITE_API_URL + PATH.GOOGLE}
  // 	&response_type=code
  // 	&scope=email profile`;
  const handleOauthLogin = () => {
    location.replace(GOOGLE_URL); // 이동
    // window.open(GOOGLE_URL, '_blank', 'width=500,height=600,left=50,top=10'); // 새창
  };

  return (
    <Wrapper onClick={handleOauthLogin} type="button">
      <GoogleIcon />
      <Text>Google로 계속하기</Text>
    </Wrapper>
  );
};

const Wrapper = styled.button`
  width: 100%;
  display: flex;
  align-items: center;
  height: 48px;
  padding: 16px;
  border-radius: 4px;

  cursor: pointer;
  border: 1px solid ${({ theme: { colors } }) => colors.textTertiary};
  background-color: ${({ theme: { colors } }) => colors.white};
  transition: all 0.2s ease-in-out;
  &:hover {
    background-color: ${({ theme: { colors } }) => colors.bgGray50};
  }
`;

const Text = styled.p`
  font: ${({ theme: { fonts } }) => fonts.displayB14};
  color: ${({ theme: { colors } }) => colors.textSecondary};
  flex: 1;
`;

수정 코드

import { styled } from "styled-components";
import { GoogleIcon } from "../icon/icons";
// import { PATH } from 'constants/path';

const { MODE, VITE_GOOGLE_CLIENT_ID, VITE_REDIRECT_ADDRESS } = import.meta.env;

export const OAuthButton = () => {
  const isDev = MODE === "development";
  console.log("isDev", isDev);

  // const LOCAL_URL = 'http://localhost:5173';
  const GOOGLE_URL = `https://accounts.google.com/o/oauth2/v2/auth?scope=https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/userinfo.email&client_id=${VITE_GOOGLE_CLIENT_ID}&response_type=code&redirect_uri=${VITE_REDIRECT_ADDRESS}&access_type=offline`;
  // const GOOGLE_URL = `https://accounts.google.com/o/oauth2/v2/auth?
  // 	client_id=${VITE_GOOGLE_CLIENT_ID}
  // 	&redirect_uri=${isDev ? LOCAL_URL + PATH.GOOGLE : VITE_API_URL + PATH.GOOGLE}
  // 	&response_type=code
  // 	&scope=email profile`;
  const handleOauthLogin = () => {
    location.replace(GOOGLE_URL); // 이동
    // window.open(GOOGLE_URL, '_blank', 'width=500,height=600,left=50,top=10'); // 새창
  };

  return (
    <Wrapper onClick={handleOauthLogin} type="button">
      <GoogleIcon />
      <Text>Google로 계속하기</Text>
    </Wrapper>
  );
};

const Wrapper = styled.button`
  width: 100%;
  display: flex;
  align-items: center;
  height: 48px;
  padding: 16px;
  border-radius: 4px;

  cursor: pointer;
  border: 1px solid ${({ theme: { colors } }) => colors.textTertiary};
  background-color: ${({ theme: { colors } }) => colors.white};
  transition: all 0.2s ease-in-out;
  &:hover {
    background-color: ${({ theme: { colors } }) => colors.bgGray50};
  }
`;

const Text = styled.p`
  font: ${({ theme: { fonts } }) => fonts.displayB14};
  color: ${({ theme: { colors } }) => colors.textSecondary};
  flex: 1;
`;

문제 부분

const { MODE, VITE_GOOGLE_CLIENT_ID } = import.meta.env;

export const OAuthButton = () => {
  const isDev = MODE === 'development';
  console.log('isDev', isDev);

  const LOCAL_URL = 'http://localhost:5173';
  const GOOGLE_URL = `https://accounts.google.com/o/oauth2/v2/auth?scope=https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/userinfo.email&client_id=${VITE_GOOGLE_CLIENT_ID}&response_type=code&redirect_uri=${
    isDev ? LOCAL_URL + PATH.GOOGLE : 'https://foodymoody.site' + PATH.GOOGLE
  }&access_type=offline`;
const { MODE, VITE_GOOGLE_CLIENT_ID, VITE_REDIRECT_ADDRESS } = import.meta.env;

export const OAuthButton = () => {
    const isDev = MODE === 'development';
    console.log('isDev', isDev);

    // const LOCAL_URL = 'http://localhost:5173';
    const GOOGLE_URL = `https://accounts.google.com/o/oauth2/v2/auth?scope=https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/userinfo.email&client_id=${VITE_GOOGLE_CLIENT_ID}&response_type=code&redirect_uri=${VITE_REDIRECT_ADDRESS}&access_type=offline`;
  • VITE_GOOGLE_CLIENT_ID
    • 이전에 공유됐던 .ENV 파일만 믿고, 멋대로 백엔드에서 넘겨줬으니 괜찮다는 생각을 했던 것 같다.
      • 포스트에는 올리지 않았지만, 어제 해결했던 kakao api와 naver api 문제도 결국은 .ENV 파일의 VITE 미작성 문제였기 때문에 이 부분을 생각했어야 했다.
      • 백엔드에서의 yml 파일은, 프론트엔드에서 .ENV 파일로 구분된다는 걸 뼈저리게 느꼈다..
  • redirect_uri=${VITE_REDIRECT_ADDRESS}
    • 이 부분도 같은 이유로 제대로 작동하지 않아서 VITE로 다시 넣어주자 하고 변경했다.

2차 문제 발생

1st 해결 시도

Screenshot 2024-12-11 at 10 20 26

  • 이 화면을 보고, 잘 작동하는구나 싶었는데 플래그를 괜히 세운 것 같다.

Screenshot 2024-12-11 at 10 21 17

  • 로그인을 하면 이런 식으로 errorPage가 뜬다.

2nd 해결 시도

redirect_url 부분에서 프론트의 코드가 괜히 그렇게 되어 있던 건 아닌 거 같아서 좀 찾아봤더니 redirect 백엔드의 url과 프론트엔드의 url은 다르다고 하는 것 같다.

const isDev = MODE === "development";
console.log("isDev", isDev);

const LOCAL_URL = "http://localhost:5173";
// const GOOGLE_URL = `https://accounts.google.com/o/oauth2/v2/auth?scope=https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/userinfo.email&client_id=${VITE_GOOGLE_CLIENT_ID}&response_type=code&redirect_uri=${VITE_REDIRECT_ADDRESS}&access_type=offline`;

console.log("RedirectAddress", "https://foodymoody.store" + PATH.GOOGLE);

const GOOGLE_URL = `https://accounts.google.com/o/oauth2/v2/auth?scope=https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/userinfo.email&client_id=${VITE_GOOGLE_CLIENT_ID}&response_type=code&redirect_uri=${
  isDev ? LOCAL_URL + PATH.GOOGLE : "https://foodymoody.store" + PATH.GOOGLE
}&access_type=offline`;
  • 그래서 다시 이런 식으로 변경하고 로그를 확인하니 아래와 같이 출력됐다.
isDev false
RedirectAddress https://foodymoody.store/redirect/oauth
  • 이 부분에서 갑자기 번뜩 떠올랐다.
  • 그렇다, 승인된 리디렉션 URI 란에 이 프론트에서 사용하는 URI를 허용해주지 않은 것이다.

Screenshot 2024-12-11 at 10 43 38

3rd 해결 시도

@RequestMapping("/api/auth")
@RequiredArgsConstructor
public class AuthController {

    private final LogoutUseCase logoutUseCase;
    private final LocalLoginUseCase localLoginUseCase;
    private final TokenReissueUseCase tokenReissueUseCase;
    private final OAuthLoginUseCase oAuthLoginUseCase;

    @GetMapping("/oauth/{provider}")
    public ResponseEntity<TokenIssueResponse> oAuthLogin(
            @PathVariable String provider,
            @RequestParam("code") String code) {
        TokenIssueResponse response = oAuthLoginUseCase.login(provider, code);
        return ResponseEntity.status(HttpStatus.OK).body(response);
    }
}

// ... 생략
GOOGLE: '/api/auth/oauth/google',
  • 백엔드 코드에서는 https://foodymoody.store/api/auth/oauth/google의 경로로 받으니, 이걸 승인된 리디렉션 URI에 넣어주고 프론트 코드도 맞게 수정해 보았다.
    • 사실상 PATH.GOOGLE의 경로만 백엔드와 맞게 수정한 것이다.
  • 하지만 이렇게 백엔드와 일치시키는 게 아니라는 걸 프론트엔드의 엔드포인트를 보고 깨달았다.

4th 해결 시도 (해결)

4th 해결 시도라고는 했지만, 해결할 며칠 동안 문서 작성할 여유도 없을 정도로 정말 많은 문서도 뒤져보고, AI랑 가장 많은 대화를 나눠본 것 같다.

다음에는 무조건 이렇게만 해야겠다라는 마음으로 그동안의 실패 원인을 아주 간단하게만 작성하겠다.

const GOOGLE_URL = `https://accounts.google.com/o/oauth2/v2/auth?scope=https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/userinfo.email&client_id=${VITE_GOOGLE_CLIENT_ID}&response_type=code&redirect_uri=${
  isDev ? LOCAL_URL + PATH.GOOGLE : "https://foodymoody.store" + PATH.GOOGLE
}&access_type=offline`;
console.log("RedirectAddress", "https://foodymoody.store" + PATH.GOOGLE);
export const PATH = {
  // ... 생략
  GOOGLE: "/login/oauth2/code/google", // 원인 -> GOOGLE: '/redirect/oauth',
};
  • 문제 발생 redirect url
    • 프론트 redirect url: https://foodymoody.store/redirect/oauth
    • 백엔드 redirect url: https://foodymoody.store/login/oauth2/code/google
  • 문제 해결 redirect url
    • 프론트 redirect url: https://foodymoody.store/login/oauth2/code/google
    • 백엔드 redirect url: https://foodymoody.store/login/oauth2/code/google

결론

그냥 프론트와 백엔드 redirect url을 통일해주면 되는 문제였음..

너무나도 허탈하고 어이가 없는 수준의 문제를 며칠 동안 잡아 끌었으니 이젠 OAuth 구현할 때 절대 안 까먹겠다는 생각이 들었다.

해당 이슈 링크

Leave a comment