202212
이번주는 firebase + 구글 OAuth 로그인 + 백엔드 연결에 대해서 공부를 했다.
첫번째 수업에서는 http, https, session, cookie, JWT 토큰, firebase + google Oauth 로그인 과정에 대해 함께 수업을 들었고 이론적인 부분에 대해 공부했다.
두번째 수업에서는 미리 firebase로 구글 로그인+로그아웃까지 구현하고 본격적으로 백엔드에 토큰 전달하여 구현을 하기 위해 함께 진행했으나 백엔드 서버에 문제가 있어서 추후에 다시 맞춰보고 재진행하기로 하였다.
멘토님께 pull request를 진행했던 메인페이지 브랜치에 대해서 피드백을 받았다.
-섞여있는 함수 사용 방식들 -> 화살표 함수로 변경
-switch 문은 약간 옛 방식 느낌 -> if문 두개로 바꾸는 게 더 좋음
-리스트 내부에 snake_case로 섞여 있는 것 -> camelCase로 바꿔주기
-List 컴포넌트 props 전달 React.FC / interface 설정(공부하기)
-TypeScript pick 공부하기
firebase로 구글 로그인+로그아웃까지 구현하는 것은 한번 해본 적이 있어서 크게 어렵지 않았고, 블로그 글과 firebase 문서를 참고하여 진행하였다. -최신 버전 firebase로 진행함
그렇지만 백엔드 전달하는 건 해본 적이 없어서 많이 헤맸다.
백엔드와 같이 진행하는 것도 처음인데 토큰 사용, rest API, 익숙치않은 타입스크립트와 리덕스 툴킷까지 같이 묶어서 진행하려니 어떻게 써야할 지 뭘 모르겠는지도 모르는 느낌이었고 아직 너무 부족한 게 많다는 걸 느꼈다.
머리로는 로그인 과정이 한 50% 이해가 갈랑 말랑 하지만 구현까지 하려니까 나는 그 과정이 잘 그려지지 않았는데 만약 혼자했으면 며칠도 부족했을 것 같다. 다행히 우리 팀에 뛰어난 프론트엔드 분이 계셔서 팀원 분의 가르침 아래에서 많이 이해하고 배울 수 있었다.
*로그인 과정
우리 팀은 백엔드에서 JWT 토큰을 쿠키로 저장하여 인증하는데 사용하기로 결정했다.
*프론트엔드에서 해야할 일
1. firebase 연결 + 구글 로그인/로그아웃
import { initializeApp } from 'firebase/app'
import {
getAuth,
GoogleAuthProvider,
signInWithPopup,
signOut,
} from 'firebase/auth'
const firebaseConfig = {
apiKey: process.env.REACT_APP_API_KEY,
authDomain: process.env.REACT_APP_AUTH_DOMAIN,
projectId: process.env.REACT_APP_PROJECT_ID,
storageBucket: process.env.REACT_APP_STORAGE_BUCKET,
messagingSenderId: process.env.REACT_APP_MESSAGING_ID,
appId: process.env.REACT_APP_APP_ID,
measurementId: process.env.REACT_APP_MEASUREMENT_ID,
}
const app = initializeApp(firebaseConfig)
export const auth = getAuth()
const provider = new GoogleAuthProvider()
export const googleSignIn = async () => {
const res = await signInWithPopup(auth, provider)
}
export const googleSignOut = async () => {
const res = await signOut(auth)
}
2. 백엔드에 토큰 전달 시 필요한 axios 함수 작성
import axios from 'axios'
interface SignInParam {
token: string
}
const signIn = async ({ token }: SignInParam) => {
const res = await axios.get('/users/me', {
headers: { Authorization: `${token}` },
})
return res
}
const signOut = async ({ token }: SignInParam) => {
const res = await axios.delete('/users/me', {
headers: { Authorization: `${token}` },
})
return res
}
// 이 함수를 불러와서 사용할 때는 authService.signIn(토큰)으로 사용 가능
export const authService = { signIn, signOut }
3. 최상단에서 onAuthStateChanged로 로그인한 사용자를 가져오기 -> 사용자의 토큰을 가져와서 백엔드 전달(get) -> dispatch로 유저 정보 redux에 넣어주기
useEffect(() => {
// 로그인한 사용자 감지
const onAuthListener = onAuthStateChanged(auth, async (user) => {
if (user) {
// 토큰 가져오기
const token: string = await user.getIdToken()
// 토큰 가져와서 백엔드로 전달(get) - 2번 참고
if (token) {
const res = await authService.signIn({ token })
// 유저 정보 리덕스에 저장
dispatch(setUser({ token, email: res.data }))
}
}
})
return onAuthListener
}, [])
4. 로그인
import { googleSignIn } from '@services/firebaseAuth'
interface ModalProps {
setModalOpen: React.Dispatch<React.SetStateAction<boolean>>
}
const LoginModal = ({ setModalOpen }: ModalProps) => {
const logIn = async () => {
await googleSignIn() // 구글 로그인 진행
setModalOpen(false)
}
return (
<LoginWrap>
<LoginBox>
<CloseBtn onClick={() => setModalOpen(false)}>X</CloseBtn>
<Content>
<div className="logo">Logo</div>
<LoginBtn onClick={logIn}>Google 로그인</LoginBtn>
</Content>
</LoginBox>
</LoginWrap>
)
}
export default LoginModal
5. useSelector로 필요시 유저 정보 가져와서 사용(nav에 표기하기 위함)
로그아웃 버튼 클릭시 구글 로그아웃 진행 -> 백엔드로 토큰 전달(delete) -> 리덕스에 유저 정보(null) 업데이트
const Navigation = () => {
const navigate = useNavigate()
const dispatch = useDispatch()
const user = useSelector((state: RootState) => state.user.user) // 유저 정보 가져와서 사용
const [modalOpen, setModalOpen] = useState<boolean>(false)
const signOutHandler = async () => {
googleSignOut() // 구글 로그아웃
if (user) {
try {
// 백엔드로 토큰 전달
await authService.signOut({ token: user.token })
// 리덕스에 유저 정보 업데이트
dispatch(setUser(null))
} catch (e) {
console.log(e)
}
}
}
return (
<>
<NavigationContainer>
...생략
{user ? (
<LogoutContainer>
<Avatar></Avatar>
<p>{user.email.split('@')[0]}</p>
<FunButton onClick={signOutHandler}>Logout</FunButton>
</LogoutContainer>
) : (
<FunButton
onClick={() => {
setModalOpen(true)
}
>
Login
</FunButton>
)}
{modalOpen && <LoginModal setModalOpen={setModalOpen} />}
</NavigationContainer>
<main>
<Outlet />
</main>
</>
)
}
export default Navigation
현재는 App.tsx의 useEffect 내부에 있는 부분(토큰 가져와서 백엔드로 전달하고 유저 정보 리덕스에 저장하는 부분)도 thunk로 만들었는데 hook으로 아예 useEffect 부분을 뺄지 고민중이다.