[프로젝트] React로 Firebase 소셜로그인 구현하기

MIZZU·2023년 1월 20일
3
post-thumbnail

시작

현재 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


  • setPersistencebrowserSessionPersistence를 통해 구현할 수 있었다.
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에서 로그인 상태 유지하기

아무리 데이터가 잘 유지된다고 해도 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))를 통해 해당 keyvalue를 불러오도록 했다.

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()가 존재한다면 loginStatetrue로 바꿔주고 해당 값을 이용해 UI를 유지시킨다.
  • 기존의 useState대신 해당 데이터를 recoil atom에 저장시켜 다른 페이지에서도 해당 값을 편하게 조회가능하도록 설정했다.

새로고침이 되어도, 다른 페이지로 이동해도, 사용자가 직접 로그아웃을 누르기 전 까지는 로그인이 잘 유지되고 있는 모습을 확인할 수 있다. 이렇게 기존의 우리가 알고있는 로그인-로그아웃이 완성되었다.


다른 소셜 추가하기

프로젝트의 기능명세에 의하면 우리 프로젝트는 구글, 트위터 소셜 로그인을 지원하는 것으로 작성되어 있지만, 현재는 구글과 깃허브 소셜 로그인을 지원한다. 해당 과정도 다사다난 했는데, 트위터는 다른 소셜과 다르게 Firebase에 추가 시 api key가 필요하다. 해당 key를 발급받기 위해서는 먼저 계정의 휴대폰 인증을 진행해야 했다.

(어디 한 번 시도해보자)

(어...? 나 유플러스 쓰는데 외않되)

(문자를 받은 적이 없다고!!!)


해당 과정을 며칠에 거쳐, 지금까지도 종종 시도해 보고 있지만 아직까지 해결이 안됐다. 찾아보니 최근 트위터에서 한국 전화번호 인증 오류가 빈번하게 발생하고 있다고 한다. 그 이유는 (일론 머스크) 트위터가 스팸 계정 번호 인증을 막기 위해 스팸 상습범과 대규모로 연결된 통신사를 통한 인증을 일괄 차단했고, 국내 통신사와 대규모 스팸의 의존이 무관하다는 것을 트위터가 인정해 주기 전까지는 해결이 되지 않을 것 같다고 한다. (일해라 트위터야...)


그래서 트위터에서 깃허브 소셜 로그인으로 변경하게 되었고, 해당 과정을 간단히 설명해보면,

  • 깃허브 소셜 로그인은 GithubAuthProvider를 통해 구현하며, 방법은 이전에 GoogleAuthProvider를 사용한 것과 동일하다.
  • buttononClick이벤트 발생 시, 소셜의 종류(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까지 상세히 알려주는 자료가 없어서 울며 겨자먹기로 해낸거긴 하지만 그래도 꽤나 성장한 것 같달까 후후...😇
추후에 일반 로그인과 다른 소셜도 추가해 보고 싶다. 그럼 이상!

profile
📂 내_머리 (응답없음)

0개의 댓글