이번 포스팅에서는 아래 조건을 만족하도록, Firebase의 Authentication 기능을 사용하는 방법에 대해 알아보도록 하겠습니다.
( 해당 예제는 Login, Header, Contact 컴포넌트(.jsx)와 js파일 몇 가지로 이루어짐 )
$ yarn add firebase
설치 확인은 package.json
에서 가능하다.
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파일에 숨겨줘야한다. 이 방법을 모른다면 해당 포스팅을 참고하자.
로그인 업체를 설정하려면 먼저 아래 경로를 따라간다.
Firebase > Console > 해당 project 선택 > Authentication
'Set up sign-in method'를 클릭하면 연결하고 싶은 로그인 업체를 설정할 수 있다.
이번 포스팅에선 로그인에 자주 쓰이는 구글과 깃허브를 다뤄보도록 하겠습니다.
이 외에 이메일, 핸드폰 번호 등의 다른 방법들을 사용해 보고 싶다면, 공식 사이트를 참고하세요.
구글 클릭 후 빈칸 작성 > Save 클릭
깃허브 클릭 후 하단 URL 복사한다.
새 탭으로 Github 열기 > Settings > Developer Settings > OAuth Apps > new OAuth App에서 빈칸 내용을 작성하고 하단의 Authorization callback URL에 복사했던 거 붙여넣기
이후 보여지는 Client ID와 Secret 복사 > Firebase 깃허브 설정 페이지로 돌아와 각 빈칸에 붙여 넣고 Save 클릭
다시 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('/home');
과 같이 사용하나, 데이터를 같이 보내고 싶다면 history.push({pathname:'/home',state:{id:userId}})
와 같이 객체로 함께 전달해주면 된다.
▽ history object
전달해준 데이터는 Router, Route 관련 컴포넌트의 props > value > location에 state로 들어 있다. 이는 useHistory().location.state
로 접근 가능하다.
다시 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를 참고해 확장해 나가면 된다.
✅ 다음 포스팅에서는 오늘 구현한 코드들에 실시간 데이터베이스 기능을 추가해보는 실습을 해보겠습니다 :)