MERN todo 프로젝트에서 Google OAuth 2.0을 활용한 로그인 기능을 구현했습니다.
@react-oauth/google 라이브러리를 활용하여 클라이언트에서 Google 로그인 버튼을 만들고, 서버에서는 Google API를 이용해 사용자 정보를 받아와 JWT를 발급하는 방식으로 처리했습니다.
1. Google OAuth 로그인 흐름
- 클라이언트에서 useGoogleLogin 훅을 이용해 Google 로그인 요청
- @react-oauth/google 라이브러리에서 제공하는 useGoogleLogin 훅을 사용했습니다.
- 이 훅을 이용하면 Google 로그인 창을 띄우고, 로그인 성공 시 code를 반환받을 수 있습니다.
- Google에서 code를 반환하면 이를 서버로 전송
- code는 Google OAuth 서버에서 발급하는 인증 코드로, 이를 백엔드로 보내야 합니다.
- 클라이언트에서는 response.code를 POST 요청의 body에 담아 서버로 전송합니다.
- 서버에서 Google API에 요청해 access_token을 받음
- 서버에서는 https://oauth2.googleapis.com/token 엔드포인트에 요청을 보냅니다.
- 이 요청을 통해 access_token과 refresh_token을 받을 수 있습니다.
- access_token을 사용해 Google 사용자 정보를 가져옴
- access_token은 Google API에 접근하기 위한 토큰입니다.
- 이를 이용해 https://www.googleapis.com/oauth2/v2/userinfo 엔드포인트에서 사용자 정보를 가져왔습니다.
- DB에서 해당 사용자가 있는지 확인하고 없으면 새로 생성
- 받은 사용자 정보(email, name, avatar 등)를 활용해 DB에서 해당 사용자를 찾습니다.
- 없다면 새로 생성하여 저장합니다.
- 서비스 전용 accessToken과 refreshToken을 생성하여 반환
- accessToken은 클라이언트에서 API 요청 시 사용됩니다.
- refreshToken은 httpOnly 쿠키에 저장하여 자동 로그인 및 토큰 갱신에 활용됩니다.
2. 서버: Google 로그인 API (/api/user/google)
서버에서는 express와 fetch를 활용하여 Google API에 요청하고,
JWT를 생성하여 클라이언트에서 사용할 수 있도록 했습니다.
export async function googleLogin(req, res, next) {
try {
const { code } = req.body;
if (!code) {
return res
.status(400)
.json({ message: 'Google 인증 코드가 없습니다.' });
}
const tokenResponse = await fetch(
'https://oauth2.googleapis.com/token',
{
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: new URLSearchParams({
client_id: process.env.GOOGLE_CLIENT_ID,
client_secret: process.env.GOOGLE_CLIENT_SECRET,
code,
grant_type: 'authorization_code',
redirect_uri: process.env.GOOGLE_REDIRECT_URI,
}).toString(),
}
);
const tokenData = await tokenResponse.json();
const { access_token } = tokenData;
if (!access_token) {
return res.status(400).json({
message: 'Google Access Token을 가져오지 못했습니다.',
});
}
const userInfoResponse = await fetch(
'https://www.googleapis.com/oauth2/v2/userinfo',
{
headers: { Authorization: `Bearer ${access_token}` },
}
);
const userInfo = await userInfoResponse.json();
const { id: googleId, email, name, picture } = userInfo;
await connectToDB();
let user = await User.findOne({ email });
if (!user) {
user = new User({
googleId,
email,
name,
avatar: picture,
});
await user.save();
}
const accessToken = generateAccessToken(user._id);
const refreshToken = generateRefreshToken(user._id);
res.cookie('refresh_token', refreshToken, {
httpOnly: true,
secure: true,
sameSite: 'None',
maxAge: 24 * 60 * 60 * 1000,
});
res.json({
_id: user.id,
email: user.email,
name: user.name,
avatar: user.avatar,
access_token: accessToken,
});
} catch (error) {
console.error('Google 로그인 에러:', error);
next(error);
}
}
3. 클라이언트: Google 로그인 버튼 & API 요청
@react-oauth/google 라이브러리에서 제공하는 useGoogleLogin 훅을 사용했습니다.
이 훅을 이용하면 Google 로그인 창을 띄우고, 로그인 성공 시 code를 반환받을 수 있습니다.
서버에 code를 전송하여 로그인 처리 후, JWT를 Zustand에 저장했습니다.
import { useGoogleLogin } from '@react-oauth/google';
const googleLogin = useGoogleLogin({
flow: 'auth-code',
onSuccess: async (response) => {
try {
const res = await fetch(`${API_BASE_URL}/api/user/google`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ code: response.code }),
credentials: 'include',
});
const data: { access_token?: string } = await res.json();
if (data.access_token) {
toast.success('Google 로그인 성공!');
setAccessToken(data.access_token);
navigate('/todo', { replace: true });
} else {
toast.error('Google 로그인 실패');
}
} catch (error) {
console.error('Google 로그인 에러:', error);
toast.error('Google 로그인 중 문제가 발생했습니다.');
}
},
onError: () => {
toast.error('Google 로그인 실패');
},
});
'Node.js > Todo' 카테고리의 다른 글
모바일 환경에서 Refresh Token 저장 방식 변경 (Third-Party Cookie 이슈) (0) | 2025.03.03 |
---|---|
JWT 인증 방식 개선: 쿠키 기반에서 토큰 기반으로 전환 (0) | 2025.02.24 |
회원가입 및 로그인 기능 구현 (0) | 2025.02.20 |
Todo CRUD API 구현하기 (0) | 2025.02.19 |
쿠키 기반 JWT 인증 및 로그인/로그아웃 기능 (0) | 2025.02.18 |
MERN todo 프로젝트에서 Google OAuth 2.0을 활용한 로그인 기능을 구현했습니다.
@react-oauth/google 라이브러리를 활용하여 클라이언트에서 Google 로그인 버튼을 만들고, 서버에서는 Google API를 이용해 사용자 정보를 받아와 JWT를 발급하는 방식으로 처리했습니다.
1. Google OAuth 로그인 흐름
- 클라이언트에서 useGoogleLogin 훅을 이용해 Google 로그인 요청
- @react-oauth/google 라이브러리에서 제공하는 useGoogleLogin 훅을 사용했습니다.
- 이 훅을 이용하면 Google 로그인 창을 띄우고, 로그인 성공 시 code를 반환받을 수 있습니다.
- Google에서 code를 반환하면 이를 서버로 전송
- code는 Google OAuth 서버에서 발급하는 인증 코드로, 이를 백엔드로 보내야 합니다.
- 클라이언트에서는 response.code를 POST 요청의 body에 담아 서버로 전송합니다.
- 서버에서 Google API에 요청해 access_token을 받음
- 서버에서는 https://oauth2.googleapis.com/token 엔드포인트에 요청을 보냅니다.
- 이 요청을 통해 access_token과 refresh_token을 받을 수 있습니다.
- access_token을 사용해 Google 사용자 정보를 가져옴
- access_token은 Google API에 접근하기 위한 토큰입니다.
- 이를 이용해 https://www.googleapis.com/oauth2/v2/userinfo 엔드포인트에서 사용자 정보를 가져왔습니다.
- DB에서 해당 사용자가 있는지 확인하고 없으면 새로 생성
- 받은 사용자 정보(email, name, avatar 등)를 활용해 DB에서 해당 사용자를 찾습니다.
- 없다면 새로 생성하여 저장합니다.
- 서비스 전용 accessToken과 refreshToken을 생성하여 반환
- accessToken은 클라이언트에서 API 요청 시 사용됩니다.
- refreshToken은 httpOnly 쿠키에 저장하여 자동 로그인 및 토큰 갱신에 활용됩니다.
2. 서버: Google 로그인 API (/api/user/google)
서버에서는 express와 fetch를 활용하여 Google API에 요청하고,
JWT를 생성하여 클라이언트에서 사용할 수 있도록 했습니다.
JAVASCRIPT
export async function googleLogin(req, res, next) {
try {
const { code } = req.body;
if (!code) {
return res
.status(400)
.json({ message: 'Google 인증 코드가 없습니다.' });
}
const tokenResponse = await fetch(
'https://oauth2.googleapis.com/token',
{
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: new URLSearchParams({
client_id: process.env.GOOGLE_CLIENT_ID,
client_secret: process.env.GOOGLE_CLIENT_SECRET,
code,
grant_type: 'authorization_code',
redirect_uri: process.env.GOOGLE_REDIRECT_URI,
}).toString(),
}
);
const tokenData = await tokenResponse.json();
const { access_token } = tokenData;
if (!access_token) {
return res.status(400).json({
message: 'Google Access Token을 가져오지 못했습니다.',
});
}
const userInfoResponse = await fetch(
'https://www.googleapis.com/oauth2/v2/userinfo',
{
headers: { Authorization: `Bearer ${access_token}` },
}
);
const userInfo = await userInfoResponse.json();
const { id: googleId, email, name, picture } = userInfo;
await connectToDB();
let user = await User.findOne({ email });
if (!user) {
user = new User({
googleId,
email,
name,
avatar: picture,
});
await user.save();
}
const accessToken = generateAccessToken(user._id);
const refreshToken = generateRefreshToken(user._id);
res.cookie('refresh_token', refreshToken, {
httpOnly: true,
secure: true,
sameSite: 'None',
maxAge: 24 * 60 * 60 * 1000,
});
res.json({
_id: user.id,
email: user.email,
name: user.name,
avatar: user.avatar,
access_token: accessToken,
});
} catch (error) {
console.error('Google 로그인 에러:', error);
next(error);
}
}
3. 클라이언트: Google 로그인 버튼 & API 요청
@react-oauth/google 라이브러리에서 제공하는 useGoogleLogin 훅을 사용했습니다.
이 훅을 이용하면 Google 로그인 창을 띄우고, 로그인 성공 시 code를 반환받을 수 있습니다.
서버에 code를 전송하여 로그인 처리 후, JWT를 Zustand에 저장했습니다.
JAVASCRIPT
import { useGoogleLogin } from '@react-oauth/google';
const googleLogin = useGoogleLogin({
flow: 'auth-code',
onSuccess: async (response) => {
try {
const res = await fetch(`${API_BASE_URL}/api/user/google`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ code: response.code }),
credentials: 'include',
});
const data: { access_token?: string } = await res.json();
if (data.access_token) {
toast.success('Google 로그인 성공!');
setAccessToken(data.access_token);
navigate('/todo', { replace: true });
} else {
toast.error('Google 로그인 실패');
}
} catch (error) {
console.error('Google 로그인 에러:', error);
toast.error('Google 로그인 중 문제가 발생했습니다.');
}
},
onError: () => {
toast.error('Google 로그인 실패');
},
});
'Node.js > Todo' 카테고리의 다른 글
모바일 환경에서 Refresh Token 저장 방식 변경 (Third-Party Cookie 이슈) (0) | 2025.03.03 |
---|---|
JWT 인증 방식 개선: 쿠키 기반에서 토큰 기반으로 전환 (0) | 2025.02.24 |
회원가입 및 로그인 기능 구현 (0) | 2025.02.20 |
Todo CRUD API 구현하기 (0) | 2025.02.19 |
쿠키 기반 JWT 인증 및 로그인/로그아웃 기능 (0) | 2025.02.18 |