현재 4명의 프론트 인원으로 swap()
이라는 사이드 프로젝트를 진행하고 있다.
때문에 백엔드 인원 없이 간단한 방식으로 로그인을 구현할 수 있는 방안이 필요했는데,
Firebase에서 제공해주는 소셜로그인을 이용하는 것으로 결정됐다.
Firebase - Authentication
에서 간단하게 등록할 수 있다.
파이어베이스는 문서가 잘되어있다.
때문에 문서를 읽어보면서 하나씩 따라하면 큰 뼈대는 무사히 세울 수 있다.
(모든 것을 알려준다고는 하지 않았다...)
https://firebase.google.com/docs/auth/web/google-signin?hl=ko&authuser=0
signInWithPopup
을 호출하여 팝업창으로 로그인 할 수 있다.useState
로 로그인 한 사용자의 user 데이터를 저장하도록 했다.import React, { useState } from 'react';
import styled from 'styled-components';
import { getAuth, signInWithPopup, GoogleAuthProvider } from 'firebase/auth';
// 스타일드 컴포넌트 코드 생략
const Test = () => {
const [userInfo, setUserInfo] = useState('');
const auth = getAuth();
const provider = new GoogleAuthProvider();
const handleGoogleLogin = () => {
signInWithPopup(auth, provider)
.then(result => {
setUserInfo(result.user);
})
.catch(error => {
console.log(error);
});
};
return (
<>
{/* JSX 코드 생략 */}
</>
);
};
export default Test;
signOut
기능을 호출하면 로그아웃 기능을 바로 구현할 수 있다.reload()
를 추가했다.import {
getAuth,
signInWithPopup,
GoogleAuthProvider,
signOut,
} from 'firebase/auth';
const handleGoogleLogout = () => {
signOut(auth)
.then(() => {
window.location.reload();
})
.catch(error => {
console.log(error);
});
};
무사히 작성했다고 생각했는데, 단순히 새로고침을 하기만 해도 로그인이 풀렸다. 많은 페이지들을 이동하는 swap()
프로젝트의 특성상 사용자의 로그인 상태 유지는 필수였다. 구글 소셜로그인과 관련한 페이지는 모두 읽은 것 같은데, 무엇을 빼먹었을까?
사실 로그인한 사용자의 정보는 별도의 로그아웃 처리를 하지않는 이상 IndexedDB
내부에 계속 존재하고 있다. 다만, 이 데이터가 존재하고 있는지 여부가 새로고침 시 증발하는 듯 했다. 해당 기능을 구현해 줄 필요가 있어 보인다.
인증 상태 지속성 문서에 살펴보면 local
, session
, none
세 가지 인증 상태 지속성 유형을 지원하고 있다. 사용자가 현재 사용중인 탭을 종료하면 로그인 지속을 종료하고 싶었기 때문에 session
을 사용했다.
https://firebase.google.com/docs/auth/web/auth-state-persistence?hl=ko&authuser=0
현재 9버전을 기준으로 작성하고 있기 때문에, sessionStorage
에 사용자 정보를 저장하기 위해서는 browserSessionPersistence
를 사용해야 한다. (이걸 공식문서에서 명시를 안해주네 ^ㅗ^ 킹갓 스택오버플로우)
https://stackoverflow.com/questions/69038593/how-to-use-setpersistence-in-firebase-modular-sdk-v9
setPersistence
와 browserSessionPersistence
를 통해 구현할 수 있었다.import {
getAuth,
signInWithPopup,
GoogleAuthProvider,
signOut,
setPersistence,
browserSessionPersistence,
} from 'firebase/auth';
const handleGoogleLogin = () => {
setPersistence(auth, browserSessionPersistence)
.then(() => {
return signInWithPopup(auth, provider)
.then(() => {
// 로그인 성공
})
.catch(error => {
console.log(error);
});
})
.catch(error => {
console.log(error);
});
};
이제 로그인 시 사용자의 정보가 sessionStorage
내에 저장이 되고, 로그아웃 시 저장된 데이터가 사라지는 것을 확인할 수 있다.
아무리 데이터가 잘 유지된다고 해도 UI에서 그려지지 않으면 사용자는 상태를 알 수가 없다. sessionStorage
에 저장해 둔 사용자 데이터가 존재한다면 계속해서 UI를 유지시키도록 하자.
const sessionUserData = () => {
for (const key of Object.keys(sessionStorage)) {
if (key.includes('firebase:authUser:')) {
return JSON.parse(sessionStorage.getItem(key));
}
}
};
const key of Object.keys(sessiongStorage)
를 통해 sessionStorage
내부에 있는 모든 key
값을 순회한다.key
의 이름이 'firebase:authUser:'
를 포함하고 있다면 JSON.parse(sessionStorage.getItem(key))
를 통해 해당 key
의 value
를 불러오도록 했다.import React, { useState, useEffect } from 'react';
import { useRecoilValue, useSetRecoilState } from 'recoil';
import { userInfo } from '../../atoms/atoms';
const setLoginUserData = useSetRecoilState(userInfo);
const [loginState, setLoginState] = useState(false);
useEffect(() => {
const userData = sessionUserData();
if (userData) {
setLoginState(true);
setLoginUserData(userData);
}
}, []);
useEffect
를 사용해서 페이지 렌더링 시에 sessionUserData()
가 존재한다면 loginState
를 true
로 바꿔주고 해당 값을 이용해 UI를 유지시킨다.useState
대신 해당 데이터를 recoil atom
에 저장시켜 다른 페이지에서도 해당 값을 편하게 조회가능하도록 설정했다.새로고침이 되어도, 다른 페이지로 이동해도, 사용자가 직접 로그아웃을 누르기 전 까지는 로그인이 잘 유지되고 있는 모습을 확인할 수 있다. 이렇게 기존의 우리가 알고있는 로그인-로그아웃
이 완성되었다.
프로젝트의 기능명세에 의하면 우리 프로젝트는 구글, 트위터 소셜 로그인
을 지원하는 것으로 작성되어 있지만, 현재는 구글과 깃허브 소셜 로그인
을 지원한다. 해당 과정도 다사다난 했는데, 트위터는 다른 소셜과 다르게 Firebase에 추가 시 api key
가 필요하다. 해당 key
를 발급받기 위해서는 먼저 계정의 휴대폰 인증을 진행해야 했다.
(어디 한 번 시도해보자)
(어...? 나 유플러스 쓰는데 외않되)
(문자를 받은 적이 없다고!!!)
해당 과정을 며칠에 거쳐, 지금까지도 종종 시도해 보고 있지만 아직까지 해결이 안됐다. 찾아보니 최근 트위터에서 한국 전화번호 인증 오류가 빈번하게 발생하고 있다고 한다. 그 이유는 (일론 머스크) 트위터가 스팸 계정 번호 인증을 막기 위해 스팸 상습범과 대규모로 연결된 통신사를 통한 인증을 일괄 차단했고, 국내 통신사와 대규모 스팸의 의존이 무관하다는 것을 트위터가 인정해 주기 전까지는 해결이 되지 않을 것 같다고 한다. (일해라 트위터야...)
그래서 트위터에서 깃허브 소셜 로그인으로 변경하게 되었고, 해당 과정을 간단히 설명해보면,
GithubAuthProvider
를 통해 구현하며, 방법은 이전에 GoogleAuthProvider
를 사용한 것과 동일하다.button
에 onClick
이벤트 발생 시, 소셜의 종류(name
)가 Google
이라면 googleProvider
를 실행하고, 아니라면 githubProvider
를 실행하도록 했다.useNavigate
를 사용해 이전 페이지로 다시 이동하도록 설정했다.import React from 'react';
import { useNavigate } from 'react-router-dom';
import {
getAuth,
signInWithPopup,
GoogleAuthProvider,
GithubAuthProvider,
setPersistence,
browserSessionPersistence,
} from 'firebase/auth';
const SocialBtn = ({ background, color, icon, name }) => {
const currentURL = window.location.href.split('/');
const pathName = currentURL[currentURL.length - 1];
const firstLetter = pathName.charAt(0).toUpperCase();
const otherLetters = pathName.slice(1);
const navigate = useNavigate();
const auth = getAuth();
const googleProvider = new GoogleAuthProvider();
const githubProvider = new GithubAuthProvider();
const handleLogin = provider => {
setPersistence(auth, browserSessionPersistence)
.then(() => {
return signInWithPopup(auth, provider)
.then(() => {
navigate(-1);
})
.catch(error => {
handleErrorMsg(error);
});
})
.catch(error => {
handleErrorMsg(error);
});
};
return (
<SocialButton
onClick={
name === 'Google'
? () => {
handleLogin(googleProvider);
}
: () => {
handleLogin(githubProvider);
}
}
background={background}
color={color}
>
{icon}
<span>
{firstLetter + otherLetters} with {name}
</span>
</SocialButton>
);
};
export default SocialBtn;
이렇게 Firebase - Users
에서 소셜 종류 별로 로그인 한 사용자 목록을 확인할 수 있고, 사용자 관리도 가능하다. 이렇게 무사히 마무리 되는 줄 알았으나 새로운 에러는 언제나 발생을 하고 마는데...
사용자가 인증에 사용한 소셜의 종류는 다를지라도 사용하는 이메일 계정이 동일한 경우가 있다. 사용자가 동일 이메일로 이미 로그인 한 이력이 있는 경우에 다른 소셜을 통해 로그인을 시도하면, auth/account-exists-with-different-credential Firebase: Error (auth/account-exists-with-different-credential).
이라는 에러를 콘솔에 띄우며 로그인이 되지 않는다.
해결 방법은 의외로 간단했는데, Firebase - Settings
에서 동일한 이메일을 사용하는 계정 연결
을 ID 공급업체별로 여러 계정 만들기
로 바꿔주면 바로 해결된다. 위의 Users
이미지 처럼 동일 계정, 다른 소셜의 형태로 이용 가능하다.
문서를 정독하면서 스스로 특정 기능을 처음부터 끝까지 구현해 본 것은 처음인 것 같다.
사실 구글링을 해봐도 문서 외적으로는 A-Z까지 상세히 알려주는 자료가 없어서 울며 겨자먹기로 해낸거긴 하지만 그래도 꽤나 성장한 것 같달까 후후...😇
추후에 일반 로그인과 다른 소셜도 추가해 보고 싶다. 그럼 이상!