React 애플리케이션에 Google 인증 기능을 추가할 때, react-google-login 라이브러리의 Hook API인 useGoogleLogin과 useGoogleLogout은 컴포넌트 기반 접근법보다 더 유연하고 간결한 코드를 작성할 수 있게 해줍니다. 이 문서에서는 해당 Hook들의 초기 설정부터 고급 시나리오까지의 구현 방법을 상세히 다루며, 코드 구조와 변수 명명을 변경하여 실제 프로젝트 적용 시 참고할 수 있는 최적화된 패턴을 제시합니다.
Hook 기반 인증의 장점
전통적인 <GoogleLogin /> 컴포넌트를 사용하는 대신 Hook을 활용하면 다음과 같은 이점을 얻을 수 있습니다:
- 상태 관리 분리: 인증 로직을 UI 렌더링과 분리하여 관심사 분리 원칙(Separation of Concerns)을 준수합니다.
- 재사용성 향상: 커스텀 버튼이나 특정 조건부 렌더링 등 다양한 UI 요소에 쉽게 통합할 수 있습니다.
- 성능 개선: 필요할 때만 스크립트를 로드하고 상태를 업데이트하므로 불필요한 리렌더링을 최소화합니다.
환경 설정 및 기본 구현
먼저 패키지를 설치하고 Google Cloud Console에서 OAuth 클라이언트 ID를 발급받아야 합니다.
npm install react-google-login --save
가장 기본적인 로그인 버튼 구현 예제입니다. 여기서는 authConfig 객체를 통해 옵션을 명확하게 구분하여 가독성을 높였습니다.
import React from 'react';
import { useGoogleLogin } from 'react-google-login';
// 환경 변수 또는 상수로 관리하는 것이 좋습니다.
const GOOGLE_CLIENT_ID = process.env.REACT_APP_GOOGLE_CLIENT_ID;
function SimpleAuthButton() {
const authOptions = {
clientId: GOOGLE_CLIENT_ID,
onSuccess: (response) => {
console.log('인증 완료:', response);
// 여기서 백엔드 API 호출이나 상태 업데이트 수행
},
onFailure: (error) => {
console.error('인증 오류 발생:', error);
}
};
// signIn 함수는 클릭 이벤트 핸들러로 직접 연결됩니다.
const { signIn } = useGoogleLogin(authOptions);
return (
<button
className="btn-google-auth"
onClick={signIn}
>
Google 계정으로 계속하기
</button>
);
}
useGoogleLogin 파라미터 심층 분석
Hook의 동작을 세밀하게 제어하기 위해 주요 구성 요소를 이해해야 합니다.
| 설정 키 | 타입 | 설명 |
|---|---|---|
clientId |
string | (필수) Google Developer Console에서 생성한 OAuth 클라이언트 ID. |
onSuccess |
function | (필수) 사용자가 성공적으로 인증되었을 때 실행되는 콜백. 응답 객체에는 토큰 및 사용자 프로필 정보가 포함됩니다. |
onFailure |
function | (필수) 인증 과정 중 오류가 발생했을 때 실행되는 콜백. |
scope |
string | 요청할 권한 범위. 기본값은 'profile email'이며, 캘린더나 드라이브 접근 등이 필요하면 추가해야 합니다. |
uxMode |
string | 'popup'(기본값) 또는 'redirect'. 모바일 웹뷰 등에서 팝업이 차단될 경우 redirect 모드를 사용합니다. |
autoLoad |
boolean | 컴포넌트가 마운트될 때 자동으로 로그인 창을 띄울지 여부. |
isSignedIn |
boolean | 이미 로그인된 사용자를 감지하여 즉시 onSuccess를 트리거할지 여부. |
고급 시나리오: 오프라인 액세스 및 리다이렉트 모드
서버 사이드에서 Refresh Token을 사용하여 장기적인 접근 권한을 유지해야 하는 경우, responseType을 'code'로 설정하고 서버로 권한 코드를 전송하는 방식을 사용합니다.
const handleCodeExchange = async (code) => {
// 백엔드 엔드포인트로 권한 코드(code)를 보내 Access/Refresh Token을 교환
try {
const res = await fetch('/api/auth/google/exchange', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ code })
});
if (!res.ok) throw new Error('Token exchange failed');
const data = await res.json();
// 세션 쿠키 설정 또는 로컬 스토리지 저장
} catch (err) {
console.error('Server-side token exchange error:', err);
}
};
const advancedLoginConfig = {
clientId: GOOGLE_CLIENT_ID,
scope: 'https://www.googleapis.com/auth/calendar.readonly',
responseType: 'code', // Authorization Code Flow 사용
uxMode: 'redirect', // 리다이렉트 방식 사용
redirectUri: `${window.location.origin}/callback`, // 반드시 Google Console에 등록되어야 함
prompt: 'consent', // 항상 동의 화면 표시 (Refresh Token 확보용)
accessType: 'offline', // 오프라인 액세스 요청
onSuccess: (response) => {
if (response.code) {
handleCodeExchange(response.code);
}
},
onFailure: (err) => console.warn('OAuth flow failed', err),
onAutoLoadFinished: (isLoggedIn) => {
console.log(`자동 로드 완료. 로그인 상태: ${isLoggedIn}`);
}
};
const { signIn, loaded } = useGoogleLogin(advancedLoginConfig);
로그아웃 처리: useGoogleLogout
단순히 프론트엔드의 상태를 제거하는 것뿐만 아니라, Google 세션을 완전히 종료하고 브라우저 캐시 데이터를 정리해야 할 때 useGoogleLogout을 사용합니다.
import { useGoogleLogout } from 'react-google-login';
function SessionTerminator() {
const logoutConfig = {
clientId: GOOGLE_CLIENT_ID,
onLogoutSuccess: () => {
// 1. 로컬 저장소 데이터 삭제
localStorage.removeItem('userProfile');
sessionStorage.clear();
// 2. Redux/MobX 등 전역 상태 초기화 (dispatch 등)
// store.dispatch(resetUserState());
// 3. 메인 페이지로 리다이렉트
window.location.href = '/home';
},
onFailure: () => {
alert('세션 종료에 실패했습니다. 다시 시도해주세요.');
}
};
const { signOut } = useGoogleLogout(logoutConfig);
return (
<button onClick={signOut} className="btn-logout">
로그아웃
</button>
);
}
실무 적용: 완전한 인증 컨테이너 컴포넌트
로그인과 로그아웃 로직을 하나의 컴포넌트로 캡슐화하고, 사용자 정보 유무에 따라 UI를 분기하는 패턴입니다. 이는 재사용 가능한 인증 위젯으로 활용될 수 있습니다.
import React, { useState, useEffect } from 'react';
import { useGoogleLogin, useGoogleLogout } from 'react-google-login';
const CLIENT_ID = process.env.REACT_APP_GOOGLE_CLIENT_ID;
export default function AuthContainer() {
// 로컬 스토리지에서 이전 세션 복원
const [currentUser, setCurrentUser] = useState(() => {
const stored = localStorage.getItem('google_user_data');
return stored ? JSON.parse(stored) : null;
});
// 로그인 Hook 설정
const loginResponse = useGoogleLogin({
clientId: CLIENT_ID,
onSuccess: (resp) => {
const profile = {
id: resp.googleId,
displayName: resp.profileObj.name,
email: resp.profileObj.email,
avatarUrl: resp.profileObj.imageUrl,
accessToken: resp.accessToken,
expiresAt: Date.now() + (resp.expiresIn * 1000)
};
setCurrentUser(profile);
localStorage.setItem('google_user_data', JSON.stringify(profile));
},
onFailure: (err) => {
console.error('Login failed:', err);
// 에러 유형별 처리 가능
}
});
// 로그아웃 Hook 설정
const logoutResponse = useGoogleLogout({
clientId: CLIENT_ID,
onLogoutSuccess: () => {
setCurrentUser(null);
localStorage.removeItem('google_user_data');
}
});
// 토큰 만료 확인을 위한 효과 (선택 사항)
useEffect(() => {
if (currentUser && currentUser.expiresAt < Date.now()) {
// 토큰 만료 시 자동 로그아웃 또는 갱신 로직
logoutResponse.signOut();
}
}, [currentUser]);
if (currentUser) {
return (
<div className="logged-in-view">
<img src={currentUser.avatarUrl} alt={currentUser.displayName} />
<p>환영합니다, {currentUser.displayName}</p>
<button onClick={logoutResponse.signOut}>세션 종료</button>
</div>
);
}
return (
<div className="logged-out-view">
<button
onClick={loginResponse.signIn}
disabled={!loginResponse.loaded}
>
{loginResponse.loaded ? "Google 로그인" : "준비 중..."}
</button>
</div>
);
}
디버깅 및 트러블슈팅
구현 과정에서 자주 발생하는 문제와 그 해결책은 다음과 같습니다.
- Popup Blocked 오류: 일부 브라우저는 사용자 제스처(클릭) 없이 열리는 팝업을 차단합니다.
signIn함수를 버튼의onClick속성에 직접 바인딩하여 사용자 인터랙션 내에서 실행되도록 보장하십시오. - CORS 오류:
redirect_uri가 Google Cloud Console의 "Authorized redirect URIs" 목록에 정확히 일치하지 않으면 인증이 실패합니다. 프로토콜(http/https)과 포트 번호까지 완벽하게 매칭되어야 합니다. - Third-party Cookies 비활성화: Chrome 등의 브라우저에서 서드파티 쿠키를 제한하는 정책을 시행 중일 수 있습니다. 이 경우
uxMode: 'redirect'를 사용하여 서버 사이드 리다이렉트 방식으로 전환하는 것이 안정적입니다. - TypeScript 타입 정의: 공식 라이브러리에는
index.d.ts가 포함되어 있어 IDE에서 자동 완성과 타입 체크를 지원하지만, 복잡한 커스텀 필드가 필요한 경우 자체 인터페이스를 확장하여 사용할 수 있습니다.
프로젝트 구조 참고
라이브러리 내부 소스는 다음과 같이 모듈화되어 있어 필요 시 Fork하여 수정하거나 기여할 수 있습니다.
src/
├── hooks/
│ ├── use-google-login.js # 메인 로그인 로직 및 스크립트 로드 관리
│ └── use-google-logout.js # 세션 해제 및 스크립트 제거 로직
├── components/
│ ├── google-login.js # 레거시 컴포넌트 호환용 래퍼
│ └── google-logout.js # 레거시 컴포넌트 호환용 래퍼
└── utils/
├── load-script.js # Google GSI 스크립트 동적 삽입 유틸리티
└── remove-script.js # 메모리 누수 방지를 위한 스크립트 정리