안녕하세요! 이번 9장에서는 Google 로그인을 FastAPI 백엔드에 연동하는 전략을 살펴보고, 간단한 React 프런트엔드 예시코드를 통해 실제 구현 방법을 안내해 드리겠습니다. 일반적인 소셜 로그인의 핵심은 OAuth2 프로토콜을 활용하여 Google 계정 정보를 인증하고, 사용자에게 편리한 로그인을 제공하는 것입니다.
아래 내용을 학습하면 Google OAuth2 기반의 소셜 로그인 과정을 이해하고, 이를 기존 FastAPI 인증 흐름(JWT, 세션 등)에 자연스럽게 연결할 수 있게 됩니다. 또한, React 프런트엔드에서 Google 로그인 버튼을 배치하고 인증 결과를 백엔드로 전송하는 간단한 예시 코드도 함께 살펴보겠습니다.
9.1. 왜 Google 로그인이 필요한가?
- 사용자 경험 향상
- 별도의 회원가입 없이 Google 계정(지메일)로 빠르게 가입 & 로그인 가능
- 비밀번호 관리 부담 감소
- 신뢰성
- 직접 비밀번호를 저장/검증하지 않고, Google의 검증 결과를 활용하므로 보안성을 높임
- 간편한 유지보수
- 소셜 로그인 구현 시, 비밀번호 재설정 로직 등 자체 구현 노력을 줄이고, 구글 인증 서버 측 기능을 그대로 이용
9.2. Google OAuth2 개념 정리
9.2.1. OAuth2 흐름 요약
- **Client(프런트엔드)**가 “Google 로그인” 버튼 클릭
- Google의 인증 페이지에서 사용자 동의를 거쳐 토큰(Authorization Code 등)을 발급
- 백엔드 서버가 해당 코드를 Google 서버에 전달하여 access_token(혹은 id_token 등)을 교환
- 백엔드는 Google API(예: https://www.googleapis.com/oauth2/v2/userinfo)를 호출해 사용자 프로필(이메일, 이름 등) 가져옴
- 백엔드가 내부적으로 사용자 정보를 확인 & 가입 처리(JWT 발급 등)
9.2.2. OAuth2 스펙과 Google
- 구글은 OAuth2 표준을 따르며, Authorization Code Flow, Implicit Flow 등 다양한 방식을 지원
- 최신 방식으로는 Google Identity Services(GIS) 라이브러리를 사용할 수 있음
- FastAPI에서는 requests나 httpx로 구글 토큰 검증 API를 호출하거나, python-social-auth 같은 라이브러리를 활용할 수 있음
9.3. FastAPI 서버 측 연동 전략
9.3.1. Google OAuth2 클라이언트 정보 등록
- Google Cloud Console 접속 → API & Services → Credentials
- “OAuth 2.0 Client IDs” 항목에서 새 OAuth 클라이언트 ID 생성
- Redirect URI 설정: “로그인 후 되돌아올 백엔드 혹은 프런트엔드 주소”
- Client ID, Client Secret 을 발급받아 환경 변수 또는 .env에 안전하게 저장
예) .env 파일:
GOOGLE_CLIENT_ID=xxxxxxxxxxxx.apps.googleusercontent.com
GOOGLE_CLIENT_SECRET=YOUR_SECRET
GOOGLE_REDIRECT_URI=http://localhost:3000/auth/google/callback
(또는 백엔드 콜백 주소를 지정할 수도 있습니다.)
9.3.2. OAuth2 Callback 엔드포인트 구현
FastAPI에서 Google 인증을 처리할 때, 크게 두 가지 방법이 있습니다:
- 백엔드가 Authorization Code를 직접 받는 방식
- 프런트엔드에서 구글 로그인 버튼을 누르면, Google에서 Authorization Code를 백엔드 콜백 URL로 전송
- 백엔드가 이 코드를 구글 서버로 교환해 유저 정보 획득
- 프런트엔드가 ID Token을 받은 뒤, 백엔드에 전달
- React 같은 프런트엔드에서 @react-oauth/google 등 라이브러리를 통해 직접 구글 토큰 발급
- 발급된 id_token(JWT)을 백엔드에 보내면, 백엔드가 이를 구글 서버에서 검증 & 프로필 조회
아래는 “백엔드가 Authorization Code를 직접 받는 방식”의 간단 예시입니다.
# app/api/v1/endpoints/auth_social.py
import os
import requests
from fastapi import APIRouter, Depends, HTTPException
from app.core.config import settings
router = APIRouter()
GOOGLE_CLIENT_ID = os.getenv("GOOGLE_CLIENT_ID")
GOOGLE_CLIENT_SECRET = os.getenv("GOOGLE_CLIENT_SECRET")
GOOGLE_REDIRECT_URI = os.getenv("GOOGLE_REDIRECT_URI")
@router.get("/google/callback")
def google_callback(code: str):
"""
구글에서 Authorization Code를 백엔드로 보내주면,
이 코드를 사용해 토큰(access_token, id_token)을 교환하고, 사용자 정보를 가져온다.
"""
# 1) code로 토큰 교환
token_endpoint = "https://oauth2.googleapis.com/token"
data = {
"code": code,
"client_id": GOOGLE_CLIENT_ID,
"client_secret": GOOGLE_CLIENT_SECRET,
"redirect_uri": GOOGLE_REDIRECT_URI,
"grant_type": "authorization_code",
}
r = requests.post(token_endpoint, data=data)
if r.status_code != 200:
raise HTTPException(status_code=400, detail="Failed to obtain token from Google")
token_json = r.json()
access_token = token_json["access_token"]
id_token = token_json.get("id_token")
# 2) 토큰으로 사용자 정보 가져오기
userinfo_endpoint = "https://www.googleapis.com/oauth2/v2/userinfo"
headers = {"Authorization": f"Bearer {access_token}"}
userinfo_resp = requests.get(userinfo_endpoint, headers=headers)
if userinfo_resp.status_code != 200:
raise HTTPException(status_code=400, detail="Failed to fetch user info from Google")
userinfo = userinfo_resp.json()
# 예: { "id": "12345", "email": "example@gmail.com", "verified_email": true, ... }
# 3) 이메일 등을 확인해 내부 DB에 사용자 정보 등록 or 기존 계정 찾기
user_email = userinfo["email"]
# ... DB 조회/생성 로직 ...
# 4) 자체 JWT(또는 세션) 발급하여 로그인 처리
# (JWT 발급 예시)
# access_token = create_access_token({"sub": user_id_in_our_db})
# 5) 프런트엔드로 리다이렉트 또는 JSON 응답
return {"msg": "Google login successful", "email": user_email}
주의: 프로덕션 환경에서는 HTTPS 사용, 에러 처리, 보안 검증 등을 더 꼼꼼히 구현해야 합니다.
9.3.3. 기존 JWT 인증 흐름과 통합
- 구글에서 받아온 이메일 또는 sub(고유 ID) 정보를 이용해 DB의 유저를 찾거나, 없으면 새로 생성
- 이후 internal JWT를 발급(예: create_access_token({"sub": user.id}))하여, 프런트엔드가 동일한 방식으로 로그인 세션을 유지할 수 있도록 함
- FastAPI의 get_current_user 로직은 그대로 사용 가능
9.4. React 프런트엔드에서의 Google Login 예시
9.4.1. Google Identity Services (@react-oauth/google)
최신 방식으로는 @react-oauth/google 라이브러리를 사용해 **토큰(Id Token, Access Token 등)**을 프런트엔드에서 받는 접근이 흔해졌습니다.
npm install @react-oauth/google
9.4.2. React 코드 스니핏
// src/App.js
import React from 'react';
import { GoogleOAuthProvider, GoogleLogin } from '@react-oauth/google';
function App() {
const handleLoginSuccess = async (credentialResponse) => {
// credentialResponse.credential: Google에서 발급한 JWT(id_token)
const idToken = credentialResponse.credential;
// 이 토큰을 백엔드로 전달하여 검증 & 내부 JWT 생성
const res = await fetch("http://localhost:8000/api/v1/auth/google-verify", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({ id_token: idToken })
});
const data = await res.json();
console.log("Server response: ", data);
// data 안에, 예를 들어 "access_token" (우리 서버 JWT) 등이 있을 수 있음
};
return (
<GoogleOAuthProvider clientId="GOOGLE_CLIENT_ID">
<div>
<h1>React + Google Login Demo</h1>
<GoogleLogin
onSuccess={handleLoginSuccess}
onError={() => { console.log('Login Failed'); }}
/>
</div>
</GoogleOAuthProvider>
);
}
export default App;
동작 방식
- GoogleLogin 컴포넌트 렌더링 시, “Sign in with Google” 버튼 표시
- 버튼 클릭 → 구글 팝업 → 사용자가 동의하면, 구글이 id_token(JWT)을 onSuccess 콜백으로 전달
- React는 받은 id_token을 백엔드로 전송 (fetch /auth/google-verify)
- 백엔드는 구글 서버에 이 id_token이 유효한지 검증한 뒤, 내부 유저 매핑 → 자체 JWT 발급
9.4.3. 백엔드: POST /auth/google-verify
# 예시: app/api/v1/endpoints/auth_social.py
from fastapi import APIRouter, HTTPException
from pydantic import BaseModel
from app.core.security import create_access_token
import requests
import os
router = APIRouter()
GOOGLE_CLIENT_ID = os.getenv("GOOGLE_CLIENT_ID")
class GoogleVerifyRequest(BaseModel):
id_token: str
@router.post("/google-verify")
def google_verify(payload: GoogleVerifyRequest):
"""
프런트엔드에서 id_token을 받아 구글 서버에서 검증하고,
유저 정보를 조회 후 내부 DB에 등록/로그인 처리.
"""
id_token = payload.id_token
# 1) 구글 OIDC verify endpoint
verify_endpoint = f"https://oauth2.googleapis.com/tokeninfo?id_token={id_token}"
resp = requests.get(verify_endpoint)
if resp.status_code != 200:
raise HTTPException(status_code=400, detail="Invalid Google token")
token_info = resp.json()
# token_info 예: { "aud": "YOUR_GOOGLE_CLIENT_ID", "email": "xxx@gmail.com", ... }
if token_info["aud"] != GOOGLE_CLIENT_ID:
raise HTTPException(status_code=400, detail="Token audience mismatch")
user_email = token_info["email"]
# DB에서 유저 찾기 or 생성
# user = get_or_create_user_by_email(user_email)
# 내부 JWT 발급
internal_token = create_access_token({"sub": user_email}) # 예시
return {"access_token": internal_token, "token_type": "bearer"}
주의: Google Identity Services를 사용하면, tokeninfo 엔드포인트(또는 google-auth 라이브러리)로 id_token 검증이 가능하지만, 프로덕션 환경에선 더 철저히 보안 체크를 해야 합니다(만료 시각 등).
9.5. 실제 실습 프로젝트에 적용하기
9.5.1. .env 설정
- GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, GOOGLE_REDIRECT_URI 등
9.5.2. 라우터 구성
- auth_social.py 파일을 만들어, /google/callback, /google-verify 등 소셜 로그인 관련 엔드포인트 집약
- api_router.include_router(auth_social.router, prefix="/auth/social", tags=["social-auth"])
9.5.3. 내부 유저 DB 모델 확장
- User 모델에 google_id, provider, 등 소셜 정보 컬럼을 추가하거나, “소셜 유저” 전용 테이블을 둘 수도 있음
- 이미 가입된 유저가 있을 경우, 이메일 주소 기반으로 동일인 식별 처리(중복 가입 방지)
9.5.4. 로그인 후 처리
- 내부 JWT 발급(또는 세션 쿠키)을 통한 FastAPI 기존 인증 흐름과 동일
- React 측에서 우리 서버의 JWT를 받아 localStorage 등 안전한 곳에 저장, 이후부터는 이 토큰을 Authorization 헤더에 달고 API 호출
9.6. 모범 사례 및 주의사항
- HTTPS 적용
- 소셜 로그인 시 보안이 중요하므로, 프로덕션은 HTTPS를 필수로 사용해야 함
- 쿠키 vs 토큰
- JWT를 헤더로 보낼지, 혹은 HTTP-Only 쿠키로 설정할지 정책 결정 (XSS 대비)
- 구글 프로필 스코프
- 필요한 정보(이메일, 이름) 외에 과도한 스코프 요청은 사용자가 거부할 수 있음
- 최소 권한 스코프(userinfo.email, userinfo.profile) 정도만 요청 권장
- 토큰 만료 처리
- Google id_token도 유효기간이 있음. 인증 시 만료 여부를 확인해야 함
- 로깅 & 모니터링
- 소셜 로그인 오류가 발생하면 사용자 경험에 직접 영향. 에러 로그와 API 응답 상황을 모니터링
- 여러 소셜 로그인 확장
- Facebook, Apple, Kakao, Naver 등 추가 구현 시, 유사한 구조로 auth_social.py 라우트를 확장
- 동일 로직(“토큰 검증 → DB 매핑 → 내부 JWT 발급”)을 재사용
9.7. ID 토큰 vs. 액세스 토큰: 혼동을 줄이는 핵심 차이
Google OAuth2(또는 여타 소셜 로그인)에서 자주 마주치는 ID 토큰(ID Token)과 액세스 토큰(Access Token)은 서로 다른 목적을 가집니다. 종종 이 둘을 혼동하여 구현에 오류가 생기기 쉬우므로, 기본 개념을 명확히 알고 넘어가는 것이 중요합니다.
9.7.1. ID 토큰 (ID Token)
- 주요 목적: 사용자 인증(Identity) 정보를 포함
- 형식: 일반적으로 JWT(JSON Web Token) 형태이며, sub(주체, 사용자 식별자)나 email, name 같은 프로필 정보가 포함
- 사용 예:
- 사용자 로그인 확인
- 프로필(이메일, 이름 등) 획득 및 DB 매핑
- 서버 측에서 ID 토큰의 서명과 만료를 검증해, 해당 사용자가 Google 인증을 정상 완료했음을 확신
- 만료 시점: 일반적으로 짧은 기간(수 분 ~ 1시간) 유지되지만, 사용처에 따라 정확한 만료시간은 다름
- 주된 특징:
- 인증을 목적으로, “누구인지(Who)”를 식별하기 위한 토큰
- 자격 증명에 가까워, 다른 API를 직접 호출하는 데 활용 불가(ex: 구글 캘린더 API 등).
- 오직 사용자 정보를 담고 있으며, 접근 권한 범위(스코프)는 ID 토큰 안에서 별도로 표현하지 않음
9.7.2. 액세스 토큰 (Access Token)
- 주요 목적: 자원(Resource) 접근 권한을 부여
- 형식: 꼭 JWT가 아닐 수 있음(구글은 OAuth2 준수 토큰 문자열로 제공). 파싱하기보다는 API 서버(Google 등)에서 유효성을 판단
- 사용 예:
- Google API(예: Gmail, Drive, Calendar)에 접근할 때, Authorization: Bearer <access_token> 방식으로 호출
- 특정 리소스(파일, 이메일 리스트)에 대한 접근 권한이 있는지를 서버가 토큰을 통해 확인
- 만료 시점: 보통 ID 토큰보다 짧게(수 분 ~ 수 시간) 설정되며, 만료된 후에는 refresh token(갱신 토큰)으로 재발급 가능
- 주된 특징:
- **인가(Authorization)**에 초점. 구글이 발행하는 접근 권한 증명
- 해당 토큰을 소지한 주체가 특정 Google 리소스에 접근 가능한지를 판별
- 여러 API 호출에 재사용 가능(만료 전까지)
9.7.3. 정리: 인증 vs. 인가
- ID 토큰: “사용자가 실제로 구글 인증을 통과했다”는 인증(Who) 증거
- 액세스 토큰: “이 사용자가 특정 리소스 또는 API에 접근할 수 있다”는 인가(What) 증거
예컨대, React 프런트엔드에서 @react-oauth/google로 로그인하면 ID 토큰을 받을 수 있는데, 이는 백엔드가 사용자의 구글 인증 여부를 확인하는 데 활용합니다. 반면, 액세스 토큰은 구글 캘린더나 드라이브 API를 호출할 때 헤더에 담아 전달해야 하므로, 별도의 API 접근이 필요하다면 액세스 토큰을 요청하고 사용해야 합니다.
Tip: 대부분 소셜 로그인(Google, Facebook, Apple) 시에는 ID 토큰으로 사용자 정보를 식별하고, 내부 시스템의 JWT(또는 세션)를 발급해 최종 로그인을 완성합니다. 추가로, “Google Drive 파일 목록을 읽기” 같은 API 요청이 필요하다면, 별도로 액세스 토큰을 받고 이를 서버-서버 혹은 클라이언트-서버 호출에 사용해야 합니다.
9.8. 마무리
이상으로 Google 로그인을 FastAPI 프로젝트에 연동하는 방법과 React 프런트엔드 예시를 간단히 살펴봤습니다.
- Google OAuth2 설정: 클라이언트 ID, 시크릿 발급 & Redirect URI 구성
- FastAPI 엔드포인트: Google로부터 받은 code 또는 id_token을 검증, 사용자 프로필 획득, 내부 JWT 발급
- React 예시: @react-oauth/google를 활용한 구글 로그인 버튼, id_token 전달 후 백엔드 검증
- 주의사항: HTTPS, 만료/보안 정책, DB 매핑 로직, 여러 소셜 로그인 확장 등
소셜 로그인을 적용하면 사용자 가입 장벽이 크게 낮아져, 빠르고 편리한 UX를 제공할 수 있습니다. 다른 소셜 서비스(페이스북, 깃허브, 트위터 등)도 OAuth2로 거의 동일하게 연동 가능하니, 필요에 따라 쉽게 확장해보시기 바랍니다.
'소프트웨어 개발 > 백엔드' 카테고리의 다른 글
| [Poetry 사용법 1편] 기본기능의 활용 (0) | 2025.01.27 |
|---|---|
| 📚[FastAPI] 8장. 종합 프로젝트 실습: 간단한 블로그 API 구현하기 (0) | 2025.01.26 |
| 📚[FastAPI] 7장. 비동기 작업 및 배포: Celery, Docker로 확장성 높이기 (0) | 2025.01.26 |
| 📚[FastAPI] 6장. 테스트 및 디버깅: Pytest 활용과 품질 보증 (0) | 2025.01.26 |
| 📚[FastAPI] 5장. 인증 및 권한 관리: JWT를 활용한 보안 강화 (0) | 2025.01.26 |