概要
Firebase の認証機構を使って、Reactでサインイン周りを実装するサンプル(メモ)です。
実装イメージはこんな感じです。
2021.04.17:追記
全体を見直しコードを若干変更しました。
ファイル構造
色々ファイルを追加するので予めファイル構造を。
- src
- components
- AuthCheck.js
- cnofig
- Firebase.js
- store
- userStore.js
- App.js
- Dashboard.js
- index.js
- Signin.js
- App.css
準備
create-react-appで雛形を用意しておいてください。
firebase、 react-router-dom、そのほかにloading中に他コンポーネントを触らせないようにする react-loading-overlayもインストールします。
# npm i -S firebase react-router-dom react-loading-overlay
firebase設定の詳細は割愛します。
configの下にFirebase.jsを作成し、利用するfirebaseの構成オブジェクトを以下のように保存しておいてください。
import firebase from 'firebase';
const firebaseConfig = {
apiKey: "api-key",
authDomain: "project-id.firebaseapp.com",
databaseURL: "https://project-id.firebaseio.com",
projectId: "project-id",
storageBucket: "project-id.appspot.com",
messagingSenderId: "sender-id",
appID: "app-id",
};
firebase.initializeApp(firebaseConfig);
export default firebase;
ユーザ情報格納部分の実装
ユーザ情報は、props
の代わりにuseContext
というhookを使って保存するようにします。contextを使うことで子コンポーネントにpropsを引き継いでいく必要がないという利点があります。ユーザ情報の変更はuseReducerを利用します。
この2つを使って userStore.js
を実装します。
import React, { createContext, useReducer } from "react";
const initialState = { user: null }
const UserContext = createContext();
const StateProvider = ({ children }) => {
const [state, dispatch] = useReducer((state, action) => {
case "login":
if (action.payload) {
return { user: action.payload.user };
}
return state;
case "logout":
return initialState;
default:
return state;
}}, initialState);
return <UserContext.Provider value={{ state, dispatch }}>{children}</UserContext.Provider>
}
export { UserContext, StateProvider }
UserContext
にユーザ情報を保持します。 StateProvider
は、子コンポーネントにコンテキストの変更を提供するためのコンポーネントで、useReducer
を利用して状態管理を来ないます。state
がuseReducerによって管理される状態
で、dispatchは状態変更を行うための関数です。
コンテキストを全体で共有できるようにする
次に作成したStateProviderをindex.jsに適応します。
全体でユーザ情報の変更を取得することができるようになります。
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import { StateProvider } from './store/userStore'
const app = (
<StateProvider>
<App />
</StateProvider>
)
ReactDOM.render(app, document.getElementById('root'));
Signinとtopページ(サインイン後)ページを作る
先にサインインページを作ります。
handleSigninでfirebaseにメールアドレスとパスワードでサインインします。(firebaseのログインプロバイダ メール/パスワード を有効にしておく必要があります)
認証に成功した場合は、dispatchでユーザ情報を設定し、トップページに移動させ、失敗した場合はアラートを表示させるようにしています。
ユーザ情報としてサインインを実行した時に戻ってきたfirebase.Userを設定しています。
import React, { useState, useContext } from "react";
import { UserContext } from "./store/userStore";
import { useHistory } from "react-router-dom";
import firebase from "./config/Firebase";
const Signin = () => {
const { state, dispatch } = useContext(UserContext);
const history = useHistory();
const [formdata, setFormdata] = useState({
email: "",
password: ""
});
const handleSignin = async () => {
try {
const auth = await firebase.auth().signInWithEmailAndPassword(formdata.email, formdata.password);
if (user) {
await dispatch({type: "login", payload: {user: auth.user }});
history.push("/");
} else {
alert("エラー!");
}
} catch (e) {
alert("認証失敗");
}
}
const handleChange = (e) => {
e.preventDefault();
setFormdata({ ...formdata, [e.target.name]: e.target.value });
}
return (
<div>
<h2>サインインページ</h2>
<div>
email: <input type="text" id="email" name="email" onChange={handleChange} value={formdata.email} />
</div>
<div>
password: <input type="password" id="password" name="password" onChange={handleChange} value={formdata.password} />
</div>
<div>
<button onClick={handleSignin}>サインイン</button><br />
</div>
</div>
)
}
export default Signin;
次に、トップページを実装します。
トップページでは、サインイン時に取得したユーザ情報(state.user.email)をcontextから取得して表示しています。
import React, { useContext } from "react";
import { UserContext } from "./store/userStore";
import firebase from "./config/Firebase";
import { useHistory } from "react-router-dom"
const Dashboard = () => {
const { state, dispatch } = useContext(UserContext);
const history = useHistory();
const handleSignout = async () => {
await firebase.auth().signOut();
dispatch({type: "logout", payload: null});
}
return (
<div>
<h2>ダッシュボード</h2>
{console.log(state.user)}
<p>こんにちは {state.user ? state.user.email : null} さん</p>
<hr />
<button onClick={handleSignout}>サインアウト</button>
</div>
)
}
export default Dashboard;
認証チェックAuthCheckの実装
firebaseにすでにサインインしているかどうかをチェックし、している場合はユーザ情報をcontextに反映、していない場合はサインインページに飛ばすようにします。
import React, { useContext, useState, useEffect } from "react";
import { UserContext } from '../store/userStore';
import { Redirect } from "react-router-dom";
import firebase from "../config/Firebase";
import LoadingOverlay from 'react-loading-overlay';
const AuthCheck = ({ children }) => {
const { state, dispatch } = useContext(UserContext);
const [checked, setChecked] = useState(false);
useEffect(() => {
const check = async () => {
firebase.auth().onAuthStateChanged(async user => {
console.log(user);
if (user) {
dispatch({type: "login", payload: { user: user }});
}
setChecked(true);
});
}
check();
}, [])
if (checked) {
if (state.user) {
return children;
}
else {
return <Redirect to="/signin" />
}
} else {
return (
<LoadingOverlay
active={true}
spinner
text='Loading...'
>
<div style={{ height: '100vh', width: '100vw' }}></div>
</LoadingOverlay>
);
}
}
export default AuthCheck;
ルーティングの設定
最後にApp.jsに各ページのルーティングを設定します。
Dashboardはサインイン後に表示させるので、Authコンポーネントで囲みます。
import React from 'react';
import './App.css';
import { BrowserRouter as Router, Route, Switch } from "react-router-dom";
import AuthCheck from './components/AuthCheck';
import Signin from './Signin';
import Dashboard from './Dashboard';
function App() {
return (
<div className="App">
<Router>
<Switch>
<Route path="/signin" name="サインイン" exact component={Signin} />
<AuthCheck>
<Route path="/" name="ダッシュボード" exact component={Dashboard} />
</AuthCheck>
</Switch>
</Router>
</div>
);
}
export default App;
これで終了です。
実行すると以下のようにサインインページが表示されます。
サインアウトするとサインインページに戻ります。
また、firebaseのサインイン情報はブラウザにキャッシュされているので、サインアウトせずにアプリを終了させ、再度アプリを起動するとサインインしたままなので、トップページ(Dashboard.js)に遷移することが確認できます。