Google OAuth 로그인 플로우를 처리하기 위한 커스텀 훅 useGoogleOAuth
를 구현했습니다.
Google 로그인 버튼 클릭 시 OAuth 인증 페이지로 리디렉션하고, 로그인 성공 후 서버로 access token을 전송하여 사용자 인증을 처리합니다.
서버 응답에서 accessToken
은 응답 헤더(Authorization
)로 받아, 이를 localStorage
에 저장한 후 적절한 경로로 리디렉션 처리합니다.
const postOAuth = async ({ code }: OAuthRequest): Promise<OAuthResponse> => {
const res = await clientInstance.post(APIPath.postOAuth, { code });
const authorizationHeader = res.headers['authorization'];
const accessToken = authorizationHeader.replace('Bearer ', '');
if (!accessToken) {
throw new Error('Authorization header is missing in the response');
}
return {
accessToken,
type: res.data.type,
profileImage: res.data.profileImage,
name: res.data.name,
};
};
export function useGoogleOAuthMutation() {
return useMutation<OAuthResponse, AxiosError, OAuthRequest>({
mutationFn: postOAuth,
});
}
Authorization
토큰을 추출하여 반환하도록 구현하였습니다.const googleClientId = import.meta.env.VITE_GOOGLE_AUTH_CLIENT_ID;
const googleRedirectUri = import.meta.env.VITE_GOOGLE_AUTH_REDIRECT_URI;
const googleAuthUri = `https://accounts.google.com/o/oauth2/v2/auth?client_id=${googleClientId}&redirect_uri=${googleRedirectUri}&response_type=code&scope=email%20profile`;
const getAccessTokenFromUrl = () => {
const searchParams = new URLSearchParams(window.location.search);
return searchParams.get('code');
};
export function useGoogleOAuth() {
const [isLoading, setIsLoading] = useState(false);
const navigate = useNavigate();
const { mutate } = useGoogleOAuthMutation();
const redirectToGoogleLogin = useCallback(() => {
window.location.href = googleAuthUri;
}, []);
const handleLogin = useCallback(() => {
const token = getAccessTokenFromUrl();
if (token) {
setIsLoading(true);
mutate(
{ code: token },
{
onSuccess: (data: OAuthResponse) => {
const { accessToken, type, profileImage, name } = data;
localStorage.setItem('token', accessToken);
localStorage.setItem('user', JSON.stringify({ profileImage, name, type }));
if (type === 'first') {
navigate(ROUTE_PATH.AUTH.SIGN_UP);
} else {
navigate(ROUTE_PATH.HOME);
}
window.location.reload();
},
onError: (error) => {
console.error('Error during login:', error);
alert('로그인에 실패했습니다. 다시 시도해주세요.');
setIsLoading(false);
},
},
);
}
}, [mutate, navigate]);
useEffect(() => {
handleLogin();
}, [handleLogin]);
return { isLoading, redirectToGoogleLogin };
}
code
를 추출하여 Mutation을 실행합니다.localStorage
에 저장합니다.localStorage
에 저장합니다.버튼 클릭시 구글 로그인 팝업으로 이동하기 위해 redirectToGoogleLogin
함수를 호출합니다.
import { useGoogleOAuth } from '@/apis/auth/mutations/useGoogleOAuth';
export function SignInButton() {
const { redirectToGoogleLogin } = useGoogleOAuth();
return (
<Button theme="outlined" onClick={redirectToGoogleLogin}>
<Flex alignItems="center" gap={FLEX_GAP_CONFIG}>
<Icon.Social.Google />
<Typo size="14px" color="gray" element="span" style={BUTTON_STYLE}>
Sign up with Google
</Typo>
</Flex>
</Button>
);
}
이후 사용자가 구글 아이디를 선택하면 LoadingPage
로 이동하게 되며 자동으로 서버에 구글 서버에서 받은 accessToken을 전송합니다.
import { Spinner, Typo } from '@/components/common';
import { useGoogleOAuth } from '@/apis/auth/mutations/useGoogleOAuth';
export default function LoadingPage() {
const { isLoading } = useGoogleOAuth();
return (
<div>
{isLoading ? (
<Spinner />
) : (
<Typo element="h1" size="18px" bold color="blue">
로그인 처리 중 오류가 발생했습니다.
</Typo>
)}
</div>
);
}