길고 긴 중앙대 멋사 위키의 소셜로그인 구현 일대기가 (거의) 끝났다 🫠
이제 어떤 식으로 위키의 프론트와 백이 협업하고 로그인 기능을 구현했는지 차근차근 정리해보자!
OAuth 2.0(Open Authorization 2.0, OAuth2)은 인증을 위한 개방형 표준 프로토콜
OAuth는 플랫폼 외부에서 개발된 서비스에게 리소스 소유자를 대신하여 리소스 서버에서 제공하는 자원에 대한 접근 권한을 위임하는 방식을 제공하는 프로토콜이다.
OAuth 2.0은 1.0과 달리 HTTPS를 통해 보안을 강화하고, 다양한 클라이언트 유형과 인증 흐름을 지원하여 확장성과 사용 편의성을 높였다.
그리고 구글, 카카오, 네이버 등이 OAuth2.0 방식을 통해 소셜로그인을 제공하고 있다.
이번 포스팅에서는 구체적인 프론트의 소셜로그인 초기 세팅에 대해서는 논의하지 않을 것이다. (이미 많은 관련 포스팅이 존재한다..)
대신 협업과정과 구현흐름을 톺아보고자 한다!
위의 흐름도는 위키 로그인의 백엔드를 맡으신 팀원이 제공해주셨다 🥹
구체적인 구현 방식의 차이는 팀원분의 블로그에 확인해볼 수 있다!
➡️ [OAuth] 프론트/백의 소셜로그인 협업
우리 프로젝트의 경우에는 유저가 로그인 버튼을 누르면
client_id
와 redirect_URI
(구글 로그인 성공 시 인가코드를 담아 리다이렉트할 경로)를 세팅한 google_URL
(구글 로그인 창 경로)로 유저를 보내주고redirect_URI
로 세팅되었던 프론트의 로그인 페이지로 유저를 리다이렉트시켜 프론트에서 인가코드를 받을 수 있도록 한다.const redirect_URI = `http://localhost:3000/googlelogin`
const CLIENT_ID = process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID
export const google_URL = `https://accounts.google.com/o/oauth2/v2/auth?client_id=${CLIENT_ID}&redirect_uri=${redirect_URI}&response_type=code&scope=email`
구글로그인 창에서 로그인을 성공했다면 위 이미지와 같이 redirect_URL
인 프론트 페이지로 리다이렉트된다. 인가코드를 담은 쿼리 스트링을 확인할 수 있다.
구글이 리다이렉트를 해주는 페이지의 컴포넌트에서 searchParams를 이용해 인가코드를 가져오고, 백엔드에게 인가코드를 넘겨주는 과정을 거친다.
백엔드 응답의 status code를 통해 분기처리를 해주었다.
200
: 해당 인가코드에 해당하는 구글 사용자 정보가 서버에 기등록 되어 있는 상태라면 백엔드에서는 2개의 jwt(access, refresh)를 보내주고, 프론트에서는 이를 local storage에 저장해 로그인을 완료한다.202
: 해당 정보가 서버에 등록되어 있지 않다면, 프론트는 해당 유저의 구글 이메일을 전역으로 저장해두어서 회원가입 과정에서 구글 이메일과 함께 백엔드에 회원가입 요청을 보낸다.우리 서비스의 경우에는 모든 구글 가입자가 서비스에 가입할 수 있는 것이 아니라, 서버에 미리 등록해둔 동아리원들의 gmail과 일치하는 유저만 가입할 수 있도록 정책이 세워졌기 때문에 위와 같이 구글 사용자의 정보를 한번 더 확인해줘야 했다.
const GoogleLogin = () => {
const route = useRouter();
const setUserEmail = useSetRecoilState(userEmailAtom);
const setUserName = useSetRecoilState(userNameAtom);
const setToken = useSetRecoilState(token)
let code: string | null = null;
// 인가코드를 받아온다
if (typeof window !== 'undefined') {
code = new URL(window.location.href).searchParams.get('code');
}
const body: postCodeBody = {
code: code,
};
useEffect(() => {
const login = async () => {
try {
if (code !== null) {
// 로그인 시도
const result = await postCode(body);
// 로그인 성공
if (result.status === '200') {
setUserEmail(result.data.email);
setUserName(result.data.name);
setToken({ access: LocalStorage.getItem('access'), refresh: LocalStorage.getItem('refresh') });
setTimeout(() => {
route.push('/');
}, 1000);
} else if (result.status === '202') {
// 회원가입 필요
setUserEmail(result.data.email);
setTimeout(() => {
route.push('/signup');
}, 1000);
}
}
} catch (error) {
alert('로그인 과정에서 문제가 발생했습니다. 다시 시도해주세요.');
setTimeout(() => {
route.push('/login');
}, 500);
}
};
login();
}, []);
return <Loading />;
};
위의 방식은 설계에 따라 달라질 수 있으며, 보안을 위해서라면 이번 프로젝트와 같이 프론트, 백 모두가 관련된 키 값을 사용하는 것보다 redirect_URI
를 백엔드로 연결하여 프론트의 키 값 노출을 최소화하는 것이 더 나을 것이라 생각한다.
다만 해당 방식으로 구현을 한다면 백엔드에서 인가코드로 처리 후 어떻게 프론트측으로 돌아올 것인가에 대한 의문이 들어, 이번 프로젝트에서는 프론트로 리다이렉트를 하도록 설계를 하게 되었다.
다음 포스팅에서는 받아온 두 개의 토큰을 활용해 어떤 식으로 안전한 로그인 환경을 구축할 수 있는지 알아보자🧨
썸네일 탐나용 로그아웃도 만들었는데 프론트 구현 "해 줘"