9. 로그인 기능 백엔드와 연결 과정에서 찾은 문제들
2025년 9월 25일
아래는 백엔드 로그인 기능 담당자(이시웅)와 대면 작업을 하며 찾은 문제점들 입니다.
목표 : 401이 떠야한다(현재 db에 사용자가 저장안되어있으므로, 401이 뜨는- 신규유저 현상이 생겨야 함)
문제 1 : 동일한 rest api key 사용안한 문제
이는 백엔드 에러 로그에서 동일한 rest api key를 사용해야 한다고 에러 로그가 상세하게 나와서, 바로 해결하였습니다.
문제 2 : 400 에러
카카오 로그인은 같은 인가 코드로 요청이 두번 이상 일어나면, 이미 사용된 인가코드, 400 에러가 발생합니다.
개발 환경은 strict mode로 두번씩 api를 호출하는 현상이 존재합니다.
이에, isProcessing이라는 값을 설정하였습니다
import React, { useEffect, useRef, useState } from 'react'; import { getKakaoAuthorizationCode, getKakaoErrorMessage, getKakaoLoginStatus, useKakaoAuth, } from '@/Apis/kakao'; import { useNavigate } from 'react-router-dom'; import { useTokenCookies } from '@/utils/cookie'; export const KakaoCallbackPage: React.FC = () => { const [status, setStatus] = useState<'loading' | 'success' | 'error'>('loading'); const [message, setMessage] = useState<string>(''); const { loginWithCode, isPending } = useKakaoAuth(); const navigate = useNavigate(); const timeout = useRef<ReturnType<typeof setTimeout> | null>(null); const { setAccessToken, setRefreshToken } = useTokenCookies(); const isProcessing = useRef(false); // 중복 처리 방지 const handleError = (errorMessage: string, shouldLog = false, error?: unknown) => { if (shouldLog && error) { console.error('❌ 로그인 실패:', error); } setStatus('error'); setMessage(errorMessage); timeout.current = setTimeout(() => { navigate('/login'); }, 3000); }; const handleLogin = async (authorizationCode: string) => { // 중복 처리 방지 if (isProcessing.current) { return; } try { isProcessing.current = true; setStatus('loading'); setMessage('로그인 처리 중...'); const result = await loginWithCode(authorizationCode); setStatus('success'); setMessage('로그인이 완료되었습니다!'); if (result.accessToken) { setAccessToken(result.accessToken, 7); setRefreshToken(result.refreshToken, 30); localStorage.setItem('userId', result.userId); } isProcessing.current = false; // 성공 시 리셋 timeout.current = setTimeout(() => { navigate('/home'); }, 3000); } catch (error) { isProcessing.current = false; // 에러 발생 시 리셋 handleError('로그인에 실패했습니다. 다시 시도해주세요.', true, error); } }; useEffect(() => { const loginStatus = getKakaoLoginStatus(); if (loginStatus === 'success') { const authorizationCode = getKakaoAuthorizationCode(); if (authorizationCode) { window.history.replaceState({}, '', '/auth/kakao/callback'); handleLogin(authorizationCode); } } else if (loginStatus === 'error') { const errorMessage = getKakaoErrorMessage(); handleError(`로그인 실패: ${errorMessage || '알 수 없는 오류'}`); } else { setMessage('카카오 로그인을 처리하고 있습니다...'); } return () => { if (timeout.current) clearTimeout(timeout.current); }; }, []); //의존성 배열 lint 경고 무시 const currentStatus = isPending ? 'loading' : status; const currentMessage = isPending ? '서버와 통신 중...' : message;
isProcessing을 사용하면, 개발환경에서의 로직은 다음과 같아집니다.
- 카카오 로그인 버튼 클릭
- 카카오 인증 완료 후 콜백 페이지 도착
- useEffect 실행 → handleLogin 호출 (1번째)
- isProcessing.current = false → true로 변경
- useEffect 실행 → handleLogin 호출 (2번째)
- if (isProcessing.current) return; ← 중복 방지
- API 요청 1번만 발생
- 로그인 성공
문제 3 : 갑자기 무슨 오류인지도 모르게, 페이지 리다이렉트 및 콘솔창도 리셋되는 문제
어느정도 해결했다 생각했는데, 이제는 분명 0.001초 정도 콘솔창에 에러가 뜨는데 이를 인지하기도 전에 페이지가 로그인 페이지로 다시 리다이렉트, 콘솔창이 리셋되는 문제가 있었습니다.
수많은 수정과 코드 복기, 등을 진행한 결과 axios.ts파일에서 문제를 파악할 수 있었습니다.
import axios, { type AxiosRequestConfig, AxiosError, type InternalAxiosRequestConfig } from 'axios'; import { apiConfig } from './config'; import { handle401Error } from '@/utils/authInterceptor'; const axiosConfig: AxiosRequestConfig = { baseURL: apiConfig.getBaseURL(), timeout: apiConfig.timeout, withCredentials: apiConfig.withCredentials, headers: apiConfig.defaultHeaders, }; export const api = axios.create(axiosConfig); api.interceptors.request.use( (config) => { const cookies = document.cookie.split(';'); const accessTokenCookie = cookies.find((cookie) => cookie.trim().startsWith('access_token=')); if (accessTokenCookie) { const accessToken = accessTokenCookie.split('=')[1]; if (accessToken) { config.headers.Authorization = `Bearer ${accessToken}`; } } return config; }, (error) => { return Promise.reject(error); }, ); api.interceptors.response.use( (response) => { return response; }, async (error: AxiosError) => { const originalRequest = error.config as InternalAxiosRequestConfig & { _retry?: boolean }; // 카카오 로그인 요청은 401 에러 처리를 건너뜀 (정상적인 응답) if (originalRequest?.url?.includes('/user/login')) { return Promise.reject(error); } if (error.response?.status === 401 && originalRequest && !originalRequest._retry) { originalRequest._retry = true; try { return await handle401Error(originalRequest); } catch (refreshError) { return Promise.reject(refreshError); } } return Promise.reject(error); }, );
다음과 같이 수정하였습니다.
제가 기존에 401 에러시 자동으로 리다이렉트 되게 한 것은, 앱을 잘 사용하다가 혹시나 모를 로그인정보 에러가 발생하면 다시 로그인을 진행하게 하려고 인터셉트 및 바로 로그인페이지로 리다이렉트 로직을 구성하였습니다.
이는, 로그인 기능 로직 안에서도 적용이 되어, 401을 받은지도 모른 상황(정상적으로 동작하는..신규유저라서 401이뜨는..)에서 바로 리다이렉트가 되는 문제였던 것입니다.
이에, /user/login 에서 401에러는 정상동작 하도록 코드를 변경하였습니다.
이후 작업
401 에러가 정상동작 하도록 코드를 변경하였으므로, 이제 401 시에는 신규유저이므로 → 닉네임 설정 페이지 이동, 정상 동작(기존 유저)일 시에는 바로 홈페이지로 이동하는 로직 분기처리를 진행해야 합니다.
정상 일때는 → navigate(/home)
에러 , 그중 401 일 때는 → navigate(/character-create)
처럼 가능하게, handleLogin을 수정하였습니다
const handleLogin = async (authorizationCode: string) => { if (isProcessing.current) { return; } try { isProcessing.current = true; setStatus('loading'); setMessage('로그인 처리 중...'); const result = await loginWithCode(authorizationCode); setStatus('success'); setMessage('로그인이 완료되었습니다!'); if (result.accessToken) { setAccessToken(result.accessToken, 7); setRefreshToken(result.refreshToken, 30); localStorage.setItem('userId', result.userId); } isProcessing.current = false; timeout.current = setTimeout(() => { navigate('/home'); }, 3000); } catch (error) { isProcessing.current = false; const axiosError = error as AxiosError; if (axiosError?.response?.status === 401) { setStatus('success'); setMessage('새로운 계정이 생성되었습니다!'); timeout.current = setTimeout(() => { navigate('/character-create'); }, 2000); } else { handleError('로그인에 실패했습니다. 다시 시도해주세요.', true, error); } } };