안녕하세요. 단단입니다.
저는 요즘 자동 포스팅 사이트를 구현하는 프로젝트를 하고 있습니다.
우선, 해당 프로젝트에 Google OAuth 인증과 인가를 구현했습니다.
저는 이전 '포잉마켓' 프로젝트에서도 Google, Kakao Talk OAuth를 구현했는데, 여기서 백엔드 개발자와 Flow를 명확하게 '소통'하는 게 중요하다는 것을 깨달았습니다.
OAuth 인증을 구현하고, access token을 저장하는 방식도 다양하기 때문에 둘이 인식한 Flow를 잘 맞추는 게 구현 시간을 단축할 수 있기 때문입니다.
그래서 오늘은 구글 OAuth 인증 후 토큰을 쿠키에 저장해 인가까지 구현한 과정을 정리해보려고 합니다.
OAuth: 유저 인증 정보를 다른 사이트에 제공하지 않으면서 제 3자가 보호된 리소스에 접근할 수 있게 하는 절차 / 인가를 위한 표준
인증: 리퀘스트한 유저가 누구인지 파악하는 기능 / ex: 사이트 가입 여부 확인
인가: 리퀘스트 내용을 요청할 권한이 있는지 확인하는 기능 / ex: 게시글 삭제 권한이 있는 유저 확인
쿠키: 서버 리스폰스나 클라이언트 코드에 따라 브라우저에 저장되는 작은 단위의 문자열 파일들
*쿠키 사용 시 장점
서버가 쿠키를 관리할 때: 처음에 쿠키를 서버에서 만들어서 인증을 구현했습니다. 이렇게 하면 CSRF 공격 등에 대응해 보안을 강화할 수 있다는 장점이 있습니다. 그러나 클라이언트에서 쿠키를 직접 조작할 수 없어 기능을 구현하는 데 불편함이 있었습니다. 로그아웃 기능을 구현할 때 클라이언트에서 쿠키를 삭제할 수 없으니 서버에 요청을 보내야 했고, 이런 부분이 유저가 많아지면 서버 부하가 증가할 수 있다고 생각했습니다. 아울러 CORS 에러가 계속 발생해 이때마다 CORS 설정을 백엔드 개발자에게 요청하기도 했습니다.
로직 수정 과정: 우선, 계속 발생하는 CORS 에러를 새결하기 위해 클라이언트에서 요청을 보낼 때 withCredentials 속성을 true로 설정했습니다. 그런데 이렇게 속성을 설정해도 서버의 설정의 수정해야 하는 상황이 계속 발생했습니다. 그래서 결국 호환성과 기능 구현 편의성을 위해 클라이언트에서 쿠키를 관리하는 로직으로 수정했습니다.
클라이언트가 쿠키를 관리할 때: 클라이언트에서 쿠키를 삭제할 수 있어 추가적인 서버 요청없이 기능 구현을 할 수 있고, 서버 부하를 줄일 수 있다는 게 장점인 것 같습니다. 아울러 요청을 보낼 때 발생하던 CORS 에러도 서버의 CORS 설정에 의존하지 않아서 관리하기 효율적이었습니다.
<Button
className="w-72 mt-20 px-4 flex items-center gap-10 h-12 bg-transparent border border-gray-300"
href={`${API_BASE_URL}/api/auth/google/login${isLocal ? '/local' : ''}`}>
<img src={googleLogo} alt="Google Logo" className="rounded-full" />
<p className="pr-8">Google로 계속하기</p>
</Button>
useEffect(() => {
if (data) {
if (data.success) {
setCookie('access_token', data.tokens.access, { path: '/', secure: true, httpOnly: true, sameSite: 'lax' });
setCookie('refresh_token', data.tokens.refresh, { path: '/', secure: true, httpOnly: true, sameSite: 'lax' });
navigate('/my');
} else {
navigate('/login');
}
}
}, [data, navigate, setCookie]);
function ProtectedRoute({ element }: { element: JSX.Element }) {
const [cookies] = useCookies(['access_token']);
return cookies.access_token && cookies.access_token !== 'undefined' ? element : <Navigate to="/login" />;
}
const router = createBrowserRouter([
{
path: '/login',
element: <Login />,
},
{
path: '/auth/google/callback',
element: <GoogleCallback />,
},
{
path: '/',
element: <ProtectedRoute element={<Layout />} />,
children: [
{
path: '',
element: <ProtectedRoute element={<My />} />,
},
{
path: 'my',
element: <ProtectedRoute element={<My />} />,
},
{
path: 'setting',
element: <ProtectedRoute element={<Setting />} />,
},
{
path: '*',
element: <Login />,
},
],
},
]);
export default router;
오늘은 인증 인가를 구현해본 경험을 정리해봤습니다.
이 글을 읽는 분들께 조금이나마 도움이 됐길 바라며, 모든 피드백 환영합니다~