Firebase - Authentication을 이용해 손쉽게 로그인 기능 구현하기 (Feat. React.js)

Noma·2021년 5월 3일
6

이번 포스팅에서는 아래 조건을 만족하도록, Firebase의 Authentication 기능을 사용하는 방법에 대해 알아보도록 하겠습니다.

( 해당 예제는 Login, Header, Contact 컴포넌트(.jsx)와 js파일 몇 가지로 이루어짐 )

  • 로그인이 완료되면 Contact 화면으로 라우팅되고, Header에 로그아웃 버튼이 뜨도록 함
  • 로그아웃하면 Login 화면으로 돌아오도록 라우팅
  • 한 번 로그인 된 기록이 있으면 Login 화면에서 바로 Contact으로 이동

1. firebase 추가하기

$ yarn add firebase

설치 확인은 package.json에서 가능하다.

2. firebase 초기화하기

Firebase에 관련된 서비스 로직들을 컴포넌트들과 따로 만들어 줄 것이므로, src폴더에 service 폴더를 생성하자.

그리고 Firebase > Console > 프로젝트 생성 > 프로젝트 > 프로젝트 설정
하단의 Firebase SDK snippet에서 세번째 script 태그 안의 내용을 복사한다.

앞서 만든 폴더 안에 firebase.js를 생성해주고 복사한 코드를 붙여 넣는다. 참고로 아래처럼 필요한 부분만 남기고 지워주는 것이 좋다.

📍 firebase.js

// src/service/firebase.js

import firebase from 'firebase/app';
import 'firebase/auth';
import 'firebase/database';

const firebaseConfig = {
    apiKey: process.env.REACT_APP_FIREBASE_API_KEY,
    authDomain: process.env.REACT_APP_FIREBASE_AUTH_DOMAIN,
    databaseURL: process.env.REACT_APP_FIREBASE_DB_URL,
    projectId: process.env.REACT_APP_FIREBASE_PROJECT_ID,
};

// Initialize Firebase
const firebaseApp = firebase.initializeApp(firebaseConfig);

export const firebaseAuth=firebaseApp.auth();

아래처럼 firebase의 모든 것을 import 하기 보다,

import firebase from 'firebase';

필요한 것만 가져오는 것이 좋다.

import firebase from 'firebase/app';
import 'firebase/auth';
import 'firebase/database';

그 다음firebaseConfig로 initialize한 firebase를 변수(firebaseApp)에 할당해주고, 이후 이것을 이용해 로그인할 수 있도록 auth()를 호출해 export해 준다.

apiKey 같은 것은 외부(Github 등..)에 노출하면 안되므로 env파일에 숨겨줘야한다. 이 방법을 모른다면 해당 포스팅을 참고하자.

3. 로그인 업체 설정

로그인 업체를 설정하려면 먼저 아래 경로를 따라간다.
Firebase > Console > 해당 project 선택 > Authentication

'Set up sign-in method'를 클릭하면 연결하고 싶은 로그인 업체를 설정할 수 있다.

이번 포스팅에선 로그인에 자주 쓰이는 구글과 깃허브를 다뤄보도록 하겠습니다.

이 외에 이메일, 핸드폰 번호 등의 다른 방법들을 사용해 보고 싶다면, 공식 사이트를 참고하세요.

3.1 구글

구글 클릭 후 빈칸 작성 > Save 클릭

3.2 깃허브

깃허브 클릭 후 하단 URL 복사한다.

새 탭으로 Github 열기 > Settings > Developer Settings > OAuth Apps > new OAuth App에서 빈칸 내용을 작성하고 하단의 Authorization callback URL에 복사했던 거 붙여넣기

이후 보여지는 Client ID와 Secret 복사 > Firebase 깃허브 설정 페이지로 돌아와 각 빈칸에 붙여 넣고 Save 클릭

4. 로그인 팝업 생성하기

다시 firebase.js로 돌아와서, 로그인 하는데 필요한 provider를 하단에 생성해주자.

여러곳에서 firebase를 import하지 않도록 하기 위해, 이곳에서 firebase 관련 모든 것을 처리하고 export 해준다.

위에서
📍 firebase.js

// src/service/firebase.js
import firebase from 'firebase/app';
import 'firebase/auth';
import 'firebase/database';

//...
export const googleProvider = new firebase.auth.GoogleAuthProvider();
export const githubProvider = new firebase.auth.GithubAuthProvider();

그러고 service 폴더에 auth.js 파일을 만들고, Firebase Docs를 참고해서 아래와 같이 작성한다.

📍 auth.js

// src/service/auth.js
import {firebaseAuth, githubProvider, googleProvider} from './firebase'; 

class Auth {
    login(name) {
        const provider = this.getProvider(name);
        return firebaseAuth.signInWithPopup(provider);
    } 
    getProvider(name){
        switch(name){
          case 'Google':
            return googleProvider;
          case 'Github':
            return githubProvider;
          default:
            throw new Error(`${name} is unknown provider.`);
        }
    }
};
export default Auth;

이렇게 firebase.js로 부터 필요한 것을 import해 작성하는 것이 좋다.

다음으로 디펜던시 인젝션(DI)을 위해 가장 상위 루트 index.js에서 Auth를 생성하고 props으로 App에 전달해준다.

📍 index.js

//src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './app';
import Auth from './service/auth';
import { BrowserRouter } from 'react-router-dom';

const auth = new Auth();

ReactDOM.render(
  <React.StrictMode>
    <BrowserRouter>
      <App auth={auth} />
    </BrowserRouter>
  </React.StrictMode>,
document.getElementById('root')
);

로그인 시 다른 화면으로 라우팅 해주기 위해 리액트 라우터를 사용한다. 라우터 사용법을 모른다면 이전 포스팅을 참고하자.

이제 화면에 보여질 컴포넌트를 만들어 준다.
📍 app.jsx

// src/app.jsx
import React from 'react';
import { Route, Switch } from 'react-router';
import Login from './components/login';
import Contact from './components/contact';

const App = ({ auth }) => {
  return (
    <div>
      <Switch>
        <Route exact path="/" >
          <Login auth={auth} />
        </Route>
        <Route path="/contact">
          <Contact auth={auth} />
        </Route>
      </Switch>
    </div>
  );
};

export default App;

📍 contact.jsx
Contact 컴포넌트는 일단 간단하게 만들고 밑에서 수정하자.

// src/components/contact.jsx
import React from 'react';
const Contact = ({ auth }) => {
    return (
        <div>Contact</div>
    );
};
export default Contact;

📍 login.jsx

// src/components/login.jsx

import React from 'react';

const Login = ({ auth }) => {
    const onLogin = (e) => {
        auth
            .login(e.target.textContent)
            .then(console.log); //🌟리턴되는 값 확인하기 위해
    }
    return (
        <div>
          <h1>Login</h1>
          <ul>
            <li><button onClick={onLogin}>Google</button></li>
            <li><button onClick={onLogin}>Github</button></li>
          </ul>
        </div>
    );
};
export default Login;

버튼을 클릭하면, 업체 이름을 Auth의 login함수에 인자로 전달해 실행해준다. 그러면 해당 업체의 로그인 창이 팝업으로 뜨게 된다.

로그인이 완료되면 다음과 같은 객체를 리턴해준다. ( 🌟표시 부분)

이 객체의 user.uid를 사용해 고유한 아이디 사용하면 로그인 여부에 따라 무언가를 처리할 수 있다.

5. 로그인 여부에 따라 화면 이동시키기 파트에서 어떻게 사용하는지 확인해 보자.


➕ history.push()로 props를 넘겨주는 방법

일반적으론 history.push('/home');과 같이 사용하나, 데이터를 같이 보내고 싶다면 history.push({pathname:'/home',state:{id:userId}})와 같이 객체로 함께 전달해주면 된다.

▽ history object

전달해준 데이터는 Router, Route 관련 컴포넌트의 props > value > location에 state로 들어 있다. 이는 useHistory().location.state로 접근 가능하다.


5. 로그인 여부에 따라 화면 이동시키기

다시 Login 컴포넌트로 돌아와, 홈 화면으로 이동시키는 함수(goToHome)를 만들어 주고, 로그인 완료 후 받아온 user의 uid를 라우팅 시 같이 전달해주자. (🌞부분)

그리고 useEffect를 통해 컴포넌트가 마운트될 때 로그인 여부를 감지해 user 정보가 있다면 다시 로그인 하지 않고, 자동으로 Home으로 넘어가게 만들어 보자. (🌈부분)

📍 login.jsx

// src/components/login.jsx

import React, { useEffect } from 'react';
import { useHistory } from 'react-router';
import Header from '../header/header';

const Login = ({ auth }) => {
    const history=useHistory();
    const onLogin = (e) => {
        auth
          .login(e.target.textContent)
          .then(data=>goToHome(data.user.uid));
    }
    const goToHome=userId=>{
    	history.push({  //🌞
            pathname:'/home',
            state:{id:userId},
        });
    }
    useEffect(()=>{
      // auth.js로 가서 onAuthChange 함수를 만들어주자.
    	auth
          .onAuthChange(user=>{ //🌈
             user && goToHome(user.uid);
        })
    });
    return (
        <section>
          <Header />
          <div>
            <h1>Login</h1>
            <ul>
              <li><button onClick={onLogin}>Google</button></li>
              <li><button onClick={onLogin}>Github</button></li>
            </ul>
          </div>
      	</section>
    );
};
export default Login;

이제 auth.js로 가서 onAuthChange함수를 만들어주자. 이 함수는 로그인 상태가 변화하면 관련 user 정보를 받아와 콜백에 전달해주는 역할을 한다.
(➕ onAuthStateChanged 메서드 사용)

🔍 logout함수는 이후 Header에서 로그아웃 버튼 클릭 시 호출해 줄 함수로 Firebase docs - signOut를 참고해 미리 만들어 놓자.

📍 auth.jsx

// src/service/auth.js
import firebase from 'firebase';
import firebaseApp from './firebase';

class Auth {
    login(providerName) {
       //...생략
    }
    logout() { //🔍
        firebaseApp.auth().signOut();
    }

    //로그인 상태가 바뀔 때마다 콜백을 호출하도록 할 수 있다.
    onAuthChange = (callback) => { 
      //여기서 callback은 위의 🌈부분이다.
      //로그아웃된 상태면 user는 null이다.
      firebaseApp.auth().onAuthStateChanged(user => {
            callback(user);
        })
    }
};
export default Auth;

📍 header.jsx

마지막으로 Header 컴포넌트를 만들고,

// src/components/header.jsx
import React from 'react';

const Header = ({ onLogout }) => {
    return (
        <header >
        //Login에 있는 Header는 onLogout을 props로 전달해주지 않았기 때문에,
        //먼저 onLogout 널체크 후 있을 경우만 로그아웃 버튼을 렌더링 해주도록 만들자.
            {onLogout && <button onClick={onLogout}>Logout</button>}
            <h1>Contact Numbers</h1>
        </header>
    );
};

export default Header;

위에서 임시로 만들어 둔 Home 컴포넌트를 아래처럼 수정해 준다.

📍 contact.jsx

// src/components/contact.jsx

import React, { useEffect, useCallback } from 'react';
import { useHistory } from 'react-router';
import Header from './header/header';
const Contact = ({ auth }) => {
    const history = useHistory();
    const onLogout = useCallback(() => {
        auth.logout();
        // 여기서 수동적으로 Contact로 이동하기보다는,
    },[auth]);
  
    useEffect(() => {
      // login.jsx에서처럼 사용자의 auth state가 변경되면 이동되도록 하자.
        auth.onAuthChange(user => {
            !user && history.push('/');
        })
    });
  
    return (
        <section>
            <Header onLogout={onLogout} />
        </section>
    );
};
export default Contact;

(Login과 달리)Contact에 있는 Header는 로그아웃 하는 함수를 props으로 받아 로그아웃 버튼이 클릭 시 해당 콜백이 실행되도록 만들었다.

여기까지 하면 처음에 정의했던 기능들이 모두 작동하게 된다. 좀 더 다양한 로그인 기능을 제공하고 싶다면, Firebase Authentication Docs를 참고해 확장해 나가면 된다.

✅ 다음 포스팅에서는 오늘 구현한 코드들에 실시간 데이터베이스 기능을 추가해보는 실습을 해보겠습니다 :)

📝 Reference

profile
Frontend Web/App Engineer

0개의 댓글