카카오 소셜 로그인은
1. /social/kakao/login 으로 이동하여 인증 진행
2. 받아온 code로 /social/kakao/callback에 GET 요청 보내서 토큰 받기
3. 토큰은 쿠키에 저장
4. 로그인 성공 후 토큰으로 유저 정보 GET 요청으로 받아옴
5. 유저 정보 Redux에 저장
이런 흐름으로 진행하였다.
// sign-in
const handleLogin = (provider: string) => {
switch (provider) {
case 'kakao':
// 서버의 카카오 로그인 엔드포인트로 이동
window.location.href = 'kakao login endpoint'; // 해당 페이지로 리디렉션
break;
case 'google':
// 서버의 구글 로그인 엔드포인트로 이동
window.location.href = 'google login endpoint';
break;
}
};
우선 provider의 종류에 따라 각 엔드포인트로 이동할 수 있게 해주었다.
이 handleLogin은 각 소셜 로그인 버튼에 연결!
리디렉션 되면 인증이 진행되고, 인증이 끝나면 리다이렉트 uri로 연결된다.
(미리 uri 등록해야 함.)
나는 /kakao/callback 에다가 콜백 컴포넌트를 만들었다.
Next.js는 파일 기반 라우팅을 사용하기 때문에, /kakao/callback 여기에 콜백 컴포넌트를 만들면
해당 경로의 엔드포인트 역할도 하게 된다!
따라서 인증이 끝난 후엔 code를 담아서 /kakao/callback으로 이동한다.
인증이 끝났으니 /social/kakao/callback 여기에 code를 담아 GET 요청을 보내야 한다.
RTK Query를 활용하여 만들어 주었다.
// userApi.ts
// 서버로 GET 요청으로 인증 코드 전달
sendKakaoCode: builder.query({
query: (code: string) => ({
url: `/social/kakao/callback?code=${code}`,
method: 'GET', // 기본값이 GET이므로 생략 가능
headers: {
Accept: 'application/json',
},
}),
}),
이제 콜백 컴포넌트를 보자.
'use client';
import { useGetUserInfoMutation, useSendKakaoCodeQuery } from '@/api/userApi';
import LoadingSpinner from '@/components/common/loadingSpinner/LoadingSpinner';
import { setUser } from '@/store/userSlice';
import { useRouter } from 'next/navigation';
import { setCookie } from 'nookies';
import { useEffect } from 'react';
import { useDispatch } from 'react-redux';
const KakaoCallback = () => {
const router = useRouter();
const params = new URLSearchParams(window.location.search);
const code = params.get('code'); // 인증 코드 추출
const dispatch = useDispatch();
const [getUserInfo] = useGetUserInfoMutation();
const { data, error, isLoading } = useSendKakaoCodeQuery(code || '');
useEffect(() => {
if (!code) {
router.push('/sign-in'); // 인증 코드가 없으면 로그인 페이지로 이동
}
}, [code, router]);
useEffect(() => {
if (data) {
console.log('토큰 정보:', data);
setCookie(null, 'access_token', data.access_token, {
maxAge: 30 * 24 * 60 * 60, // 쿠키 유효기간
path: '/', // 쿠키 경로
});
setCookie(null, 'refresh_token', data.refresh_token, {
maxAge: 30 * 24 * 60 * 60,
path: '/',
});
// 로그인 성공 후 유저 데이터 받아오기
getUserInfo({})
.unwrap()
.then(userData => {
console.log('유저 정보:', userData);
dispatch(setUser({ user: userData, loginType: 'kakao' })); // Redux에 유저 정보 저장
router.push('/'); // 홈으로 이동
})
.catch(error => {
console.error('유저 정보 요청 실패:', error);
router.push('/sign-in');
});
}
if (error) {
console.error('카카오 로그인 실패:', error);
console.log(code);
router.push('/sign-in'); // 실패 시 다시 로그인 페이지로 이동
}
}, [data, error, router, code, dispatch, getUserInfo]);
if (isLoading) return <LoadingSpinner className="hi" />;
return <></>;
};
export default KakaoCallback;
const params = new URLSearchParams(window.location.search);
const code = params.get('code'); // 인증 코드 추출
카카오 로그인을 완료하면 카카오 서버가 설정된 리디렉션 URL로 인증 코드를 전달한다.
window.location.search에서 code 파라미터를 추출하면, code 값을 얻을 수 있다.
코드가 없다면 로그인이 완료되지 않은 것이므로 로그인 페이지로 리다이렉트.
useEffect(() => {
if (!code) {
router.push('/sign-in'); // 렌더링 완료 후 실행되므로 안전
}
}, [code, router]);
useEffect를 사용하는 이유는 컴포넌트가 렌더링 된 다음에 code 값을 추출하여 확인해야 하기 때문이다.
렌더링 도중에 router.push()를 호출하면 오류가 발생할 수 있음.
인증 코드 전달
const { data, error, isLoading } = useSendKakaoCodeQuery(code || '');
아까 정의한 useSendKakaoCodeQuery로 code를 담아 GET 요청을 보낸다.
- data: 응답 데이터 (토큰 정보)
- error: 에러 정보 (요청 실패)
- isLoading: 요청 진행 상태
GET 요청이 성공했다면, data에 액세스 토큰과 리프레시 토큰이 담긴다.
토큰을 쿠키에 저장
if (data) {
setCookie(null, 'access_token', data.access_token, {
maxAge: 30 * 24 * 60 * 60,
path: '/',
});
setCookie(null, 'refresh_token', data.refresh_token, {
maxAge: 30 * 24 * 60 * 60,
path: '/',
});
}
이제 반환된 액세스 토큰과 리프레시 토큰을 쿠키에 저장해준다.
nookies 라이브러리를 사용해서 편리하게 쿠키를 설정했다.
- maxAge: 쿠키 유효기간 설정
- path: 모든 경로에서 쿠키 접근이 가능하도록 설정
maxAge는 초 단위로 설정된다.
따라서 30 * 24(h) * 60(m) * 60(m) = 30일 이 된다.
사용자 정보 요청 및 Redux 상태 업데이트
getUserInfo({})
.unwrap()
.then(userData => {
dispatch(setUser({ user: userData, loginType: 'kakao' }));
router.push('/'); // 홈으로 이동
})
.catch(error => {
router.push('/sign-in');
});
저장된 액세스 토큰을 기반으로 사용자 정보를 요청한다.
(액세스 토큰은 헤더에 담겨서 전달됨)
프로미스 형태로 반환되므로 then 체이닝으로 받아와야 한다.
(결과 값은 작업이 완료된 이후에만 사용할 수 있으므로...)
성공 시 dispatch(setUser())를 사용해서 사용자 정보와 로그인 타입을 저장한다.
(로그인 타입은 새로 추가함)
실패 시 로그인 페이지로 리다이렉트 된다. ㅠ
쿠키에 토큰 저장, 유저 데이터 받아오기, Redux에 유저 데이터 저장
이 과정 역시 useEffect가 꼭 필요하다.
왜냐면 useSendKakaoCodeQuery로 code를 전달한 후에, data(토큰)를 받아야만 다음 과정을 진행할 수 있기 때문에
data, error의 변경을 감지하고 다음 과정을 진행시킨다.
로딩 처리
if (isLoading) return <LoadingSpinner className="" />;
인증 코드 전달 및 서버 응답을 기다리는 동안엔 로딩 스피너 컴포넌트를 표시하도록 했다. (미리 만들어져 있음)
이렇게 하면 소셜 로그인 후 유저 정보까지 잘 받아와 지는 것을 볼 수 있다.
구글 로그인도 동일하게 진행하면 된당.
로그아웃 할 때는 쿠키 비우고, Redux 비우고, 로그아웃 API 요청 보내는 것 잊지 말기... ☆
아, 그리고 Redux에 저장된 정보는 변경될 때마다 알아서 업데이트가 되고, 리렌더링이 된다고 한다!
아주 편리한 녀석이군.
'개발 > 프로젝트' 카테고리의 다른 글
자동 로그인 구현하기 (0) | 2025.01.12 |
---|---|
redux-persist로 Redux 상태 유지하기 (0) | 2025.01.08 |
RTK Query로 로그인 API 구현하고 RTK로 유저 정보 관리하기 (0) | 2025.01.07 |
RTK Query를 사용하여 회원가입 API 구현하기 (1) | 2025.01.06 |
react-hook-form, zod를 활용하여 회원정보 수정 페이지 제작하기 (1) | 2025.01.04 |