문제상황
- next-auth를 사용하여 구현한 소셜로그인이 로드벨런싱이 적용된 AWS 환경에서 리다이렉트 처리가 되지 않는 문제 발생
- 프론트단에서 소셜로그인에 따라 (인가코드 발급 -> 로그인 토큰 요청 -> 사용자 정보 요청) 방식으로 SSR을 사용하여 리다이렉트 페이지 구현
1. 인가코드 발급 로그인창 요청 -> 소셜 로그인
// 요청 URL의 상세 파라미터값은 공식문서를 참고해보길 바란다.
// 구글로그인 요청 URL은 https://accounts.google.com/o/oauth2/v2/auth 이다.
const googleAuthURL = `https://accounts.google.com/o/oauth2/v2/auth?client_id=${process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID}&redirect_uri=${process.env.NEXT_PUBLIC_REDIRECT_URL}/google&response_type=code&scope=https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/userinfo.profile`;
window.location.href = googleAuthURL;
// 네이버로그인 요청 URL은 https://nid.naver.com/oauth2.0/authorize 이다.
const naverAuthURL = `https://nid.naver.com/oauth2.0/authorize?response_type=code&client_id=${process.env.NEXT_PUBLIC_NAVER_CLIENT_ID}&redirect_uri=${process.env.NEXT_PUBLIC_REDIRECT_URL}/naver&state=1234`;
window.location.href = naverAuthURL;
// 카카오 로그인 요청 URL은 https://kauth.kakao.com/oauth/authorize 이다.
const kakaoAuthURL = `https://kauth.kakao.com/oauth/authorize?response_type=code&client_id=${process.env.NEXT_PUBLIC_KAKAO_CLIENT_ID}&redirect_uri=${process.env.NEXT_PUBLIC_REDIRECT_URL}/kakao`;
window.location.href = kakaoAuthURL;
2.로그인 완료 -> SSR 로그인 엑세스 토큰 요청 -> 사용자 정보 조회
const getServerSideProps = ({ query }) => {
// query 값
{
code: 'testestestestestest', // 발급받은 인가코드
scope: 'email profile' // 범위,
authuser: '0',
prompt: 'none'
}
// 로그인 엑세스 토큰 요청
const getLoginToken = await axios({
method: 'POST',
url: 'https://oauth2.googleapis.com/token',
data: {
grant_type: 'authorization_code', // 인증 구분 값
client_id: process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID, // 개발자센터에서 발급받은 클라이언트 아이디
client_secret: process.env.NEXT_PUBLIC_GOOGLE_CLIENT_SECRET, // 개발자센터에서 발급받은 CLIENT_SECRET
redirect_uri: `${process.env.NEXT_PUBLIC_REDIRECT_URL}/google`, // 리다이렉트 URL 프론트 지정
code: query.code, // 발급받은 인가코드 넣어주기
},
headers: {
'Content-Type': 'application/x-www-form-urlencoded', // 필수 값
},
});
// 소셜 사용자 정보 조회
const getProfile = await axios({
method: 'GET',
url: 'https://www.googleapis.com/oauth2/v3/userinfo',
params: {
access_token: getLoginToken.data.access_token,
},
});
}
3.유틸 함수 분기처리 및 SSR 프롭스 리턴
// 이 함수 또한 getServerSideProps에 포함되어 있다.
// 프로파일이 있을경우로 분기를 나누었다.
if (getProfile) {
return await socialLogin('GOOGLE', getLoginToken.data, getProfile.data);
} else {
return loginRedirect('프로필 정보를 가져오지 못했습니다. 로그인페이지로 이동합니다.');
}
// 첫 번째 인자인 _provider 인자로 소셜 타입을 확인하여 이메일을 필터링한다.
// 두 번째 인자인 _tokenInfo 객체의 속성값인 access_token를 리턴해준다.
export const socialLogin = (_provider: string, _tokenInfo: any, _profileInfo: any) => {
const filterSocialEmail =
_provider === 'NAVER'
? _profileInfo.response.email
: _provider === 'KAKAO'
? _profileInfo.kakao_account.email
: _provider === 'GOOGLE' || _provider === 'FACEBOOK'
? _profileInfo.email
: '';
return {
props: {
userEmail: filterSocialEmail, // 소셜 타입에 따른 이메일
access_token: _tokenInfo.access_token, // 로그인 엑세스 토큰
},
};
};
4./auth/(google,naver,kakao).tsx 파일 -> SSR 프롭스 사용 -> 리다이렉트 함수 실행
// SSR을 제외한 리다이렉트 컴포넌트의 코드이다.
const GoogleRedirectPage: NextPage = ({ userEmail, access_token }) => {
useSocialLoginRedirect('google', userEmail, access_token);
return <></>;
};
export default GoogleRedirectPage;
// useSocialLoginRedirect === 여러 컴포넌트에서 사용 가능하게 리액트 훅으로 사용하였다.
// useRouter를 함수의 인자로 넘겨주는 이유는 SSR 구성이라 useRouter를 ts 스크립트단에서는 사용하지 못하기 때문이다.
const useSocialLoginRedirect = (_provider: string, _userEmail: string, _access_token: string) => {
const router = useRouter();
useEffect(() => {
if (_provider !== undefined && _userEmail !== undefined && _access_token !== undefined) {
homeRedirect(router, _provider, _access_token);
}
}, [_provider, _userEmail, _access_token]);
return <></>;
};
export default useSocialLoginRedirect;
// 해당 함수가 ts 파일에 있기 때문에 useRouter를 인자로 받아온다.
export const homeRedirect = (_router: NextRouter, _provider: string, _access_token: string) => {
// 엑세스토큰 여부에 따라 router.replace로 홈 리다이렉트
if (_access_token !== '') {
const expires = dayjs().add(30, 'days').toDate();
setCookie('provider', _provider, { expires, path: '/' }); // 유효기간 설정 소셜 타입 저장
setCookie('sns_access_token', _access_token, { expires, path: '/' }); // 유효기간 설정 토큰 저장
_router.replace('/');
} else {
console.log('소셜로그인 실패');
}
};