웹앱에 로그인 기능을 넣을 때 가장 먼저 고려하는 게 소셜 로그인이다. 직접 회원가입 시스템을 만들면 비밀번호 해싱, 이메일 인증, 비밀번호 찾기 등 신경 쓸 게 한두 가지가 아니다. Google OAuth를 쓰면 이런 고민을 한 번에 해결할 수 있다.
실제로 사이드 프로젝트에 Google 로그인을 붙여본 경험을 바탕으로 정리한다. 생각보다 삽질 포인트가 몇 개 있어서, 순서대로 따라하면 빠지지 않게 적어봤다.
Google Cloud Console 설정
프로젝트 생성
- Google Cloud Console에 접속
- 상단의 프로젝트 선택 → 새 프로젝트 만들기
- 프로젝트 이름을 적당히 짓고 만들기
OAuth 동의 화면 설정
이게 빠뜨리기 쉬운 부분인데, OAuth 동의 화면을 먼저 설정해야 클라이언트 ID를 만들 수 있다.
- 왼쪽 메뉴 → APIs & Services → OAuth consent screen
- User Type은 External 선택 (내부용이 아니면)
- 앱 이름, 사용자 지원 이메일, 개발자 연락처 입력
- 스코프(Scopes)에서
email,profile,openid추가 - 테스트 사용자에 본인 이메일 추가 (앱이 검증되기 전까지는 테스트 사용자만 로그인 가능)
클라이언트 ID 생성
- APIs & Services → Credentials → Create Credentials → OAuth client ID
- 애플리케이션 유형: 웹 애플리케이션
- 승인된 JavaScript 출처:
http://localhost:3000(개발용) - 승인된 리디렉션 URI:
http://localhost:3000/auth/callback - 생성하면 Client ID와 Client Secret이 나옴 → 잘 보관
여기서 삽질 포인트: 리디렉션 URI가 정확히 일치해야 한다. 끝에 슬래시 하나 차이로도 에러가 난다. http://localhost:3000/auth/callback과 http://localhost:3000/auth/callback/은 다르다.
프론트엔드 구현
Google에서 제공하는 새로운 Sign In with Google 라이브러리를 쓰는 게 가장 간단하다:
<script src="https://accounts.google.com/gsi/client" async defer></script>
<div id="g_id_onload"
data-client_id="YOUR_CLIENT_ID.apps.googleusercontent.com"
data-callback="handleCredentialResponse">
</div>
<div class="g_id_signin" data-type="standard"></div>
콜백 함수:
function handleCredentialResponse(response) {
// response.credential에 JWT 토큰이 들어있다
const token = response.credential;
// 이 토큰을 백엔드로 보내서 검증
fetch('/auth/google', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ token }),
})
.then(res => res.json())
.then(data => {
if (data.success) {
// 로그인 성공 처리
localStorage.setItem('session', data.sessionToken);
window.location.href = '/dashboard';
}
});
}
백엔드 검증 (Node.js)
프론트에서 받은 JWT 토큰을 백엔드에서 검증해야 한다. 절대 프론트에서 받은 토큰을 그대로 신뢰하면 안 된다.
import { OAuth2Client } from 'google-auth-library';
const client = new OAuth2Client(process.env.GOOGLE_CLIENT_ID);
async function verifyGoogleToken(token) {
const ticket = await client.verifyIdToken({
idToken: token,
audience: process.env.GOOGLE_CLIENT_ID,
});
const payload = ticket.getPayload();
return {
googleId: payload.sub,
email: payload.email,
name: payload.name,
picture: payload.picture,
emailVerified: payload.email_verified,
};
}
Express 라우트 예시:
app.post('/auth/google', async (req, res) => {
try {
const { token } = req.body;
const userInfo = await verifyGoogleToken(token);
// DB에서 사용자 찾기 또는 생성
let user = await db.findUserByGoogleId(userInfo.googleId);
if (!user) {
user = await db.createUser({
googleId: userInfo.googleId,
email: userInfo.email,
name: userInfo.name,
avatar: userInfo.picture,
});
}
// 세션 토큰 생성
const sessionToken = generateSessionToken(user.id);
res.json({ success: true, sessionToken });
} catch (error) {
res.status(401).json({ success: false, error: 'Invalid token' });
}
});
토큰 관리
Access Token vs Refresh Token
간단한 프로필 정보만 필요하면 ID Token으로 충분하다. 하지만 Google Calendar이나 Gmail 같은 API를 호출해야 한다면 Access Token이 필요하고, 이 토큰은 1시간 후 만료된다. 그래서 Refresh Token을 받아서 저장해둬야 한다.
Refresh Token을 받으려면 Authorization Code Flow를 써야 한다:
const authUrl = `https://accounts.google.com/o/oauth2/v2/auth?` +
`client_id=${CLIENT_ID}` +
`&redirect_uri=${REDIRECT_URI}` +
`&response_type=code` +
`&scope=openid email profile` +
`&access_type=offline` + // 이게 있어야 refresh token을 줌
`&prompt=consent`; // 매번 동의 화면을 보여줌
access_type=offline을 빼먹으면 refresh token을 안 준다. 그리고 prompt=consent를 넣어야 재로그인 시에도 refresh token을 새로 발급받을 수 있다. 이거 때문에 한참 헤맸다.
세션 관리
JWT를 세션 토큰으로 쓰는 방법도 있지만, 나는 서버사이드 세션을 선호한다. JWT는 토큰을 발급한 후 무효화하기가 어렵기 때문이다. 사용자가 로그아웃해도 토큰이 만료되기 전까지는 유효하다는 게 찜찜하다.
import crypto from 'crypto';
function generateSessionToken(userId) {
const token = crypto.randomBytes(32).toString('hex');
// Redis나 DB에 저장
sessionStore.set(token, { userId, createdAt: Date.now() });
return token;
}
프로덕션 배포 시 주의사항
- 리디렉션 URI 업데이트: localhost 대신 실제 도메인으로 변경. Cloud Console에서 추가해줘야 함
- HTTPS 필수: 프로덕션에서는 반드시 HTTPS를 써야 한다. Google이 HTTP 리디렉션 URI를 localhost 외에는 허용하지 않음
- OAuth 동의 화면 검증: 테스트 모드에서 벗어나려면 Google에 앱 검증을 요청해야 한다. 민감한 스코프를 쓰면 보안 심사도 필요
- Client Secret 관리: 절대 프론트엔드 코드에 넣지 말 것. 환경 변수로 관리
정리
Google OAuth 연동 자체는 그리 어렵지 않다. 하지만 리디렉션 URI 불일치, refresh token 미발급, 동의 화면 미설정 같은 소소한 함정들이 있어서 처음 해보면 시간을 잡아먹을 수 있다. 이 글에 정리한 순서대로 하면 큰 문제 없이 연동할 수 있을 거다.
개인적으로는 소셜 로그인만으로 인증을 처리하는 걸 선호한다. 비밀번호 관리에서 자유로워지는 것만으로도 보안 리스크가 크게 줄어든다. Google OAuth에 익숙해지면 GitHub, Kakao 같은 다른 OAuth 프로바이더를 추가하는 것도 비슷한 패턴이라 금방 할 수 있다.