前回に続きReact+Firebaseでミニアプリを作っていきます。前回の投稿はこちらから。
React + Firebaseのミニアプリを作る【メッセージ&スピナー編】
引き続きReact + Firebaseでミニアプリを作っていきます。 前回はFirebase REST APIを使ってCRUDを実装するところまで進めました。 今回はスピナー(ローディング画面)の実 ...
続きを見る
どんなアプリを作るかは【計画編】を見て下さい。
今回はFirebaseでの認証を実装していきます。
Firebase REST APIでの認証
FirebaseのREST APIを使っている場合、Firebase Auth REST APIというAPIを使って認証をすることが出来ます。
例えばユーザ登録なら、
https://identitytoolkit.googleapis.com/v1/accounts:signUp?key=[API_KEY]
このエンドポイントにPOSTメソッドでメールアドレスとパスワードを送ることでユーザ登録が出来ます。
トークン認証
ReactのようなSPA(single page application)でREST APIを用いる場合は認証に「トークン」を用いることが多いです。実際Firebase REST APIでもトークンを用いた認証を行っています。
Firebaseでは認証が必要なリクエストに対し、?auth=トークン
の形でパラメータを加えることで認証情報を加えることが出来ます。
これを使い、全てのCRUD操作で認証を必要とするよう変更を加えていきます。
ポイント
このトークンの保存方法に関しては様々な議論があります。これについては過去に記事にまとめたので、興味のある方は参考にして下さい。
詳しくはこちらから↓
React(SPA)での認証についてまとめ
Reactでの認証は厄介です。 Reactでコードを書いているとReduxやら外部ライブラリやら様々な「覚えること」「理解すべきこと」が出てきますが、結局ググればなんとかなることが多いで ...
続きを見る
準備
ルールを設定
まずはFirebase側で認証していない場合にアクセス出来ないようにロックを掛けます。
【計画編】でRealtime Databaseを作成した際には、「テストモード」で作成しました。これにより全てのリクエストが受け付けられてしまっています。
これを認証していなければリクエストを受け付けないように変更します。
FirebaseコンソールのRealtime Databaseで「ルール」タブを開き、ルールを変更します。
{ "rules": { "$uid": { ".read": "$uid === auth.uid", ".write": "$uid === auth.uid" } } }
これでuserId以下のオブジェクトは認証データと一致しなければ読み書きが出来なくなります。
【実装編2】でも述べた通り、subject, Q&A共にuserId以下にデータを保存するため認証をしていなければアクセス出来なくなります。
Sign-in methodの追加
メールとパスワードで認証ができるようにFirebaseコンソールで設定する必要があります。
Authenticationメニューから「Sign-in method」タブを選択します。
一覧の中から「メール / パスワード」の欄を編集します。
選択すると以下のようなメニューが出るので、上の方の選択を「有効にする」に変更します。
これでメールアドレスとパスワードを用いて認証する準備が出来ました。
ユーザ登録
まずはユーザ登録(Sign Up)から実装していきます。
ユーザ登録画面を作る
Material UIのTemplatesには「Sign In」ページのサンプルがあるので、コピペしつついらないものを削っていきます。
components > content > SignUpView.jsを作成します。
import React from "react"; import { useForm } from "react-hook-form"; import Avatar from "@material-ui/core/Avatar"; import PersonAddIcon from "@material-ui/icons/PersonAdd"; import Typography from "@material-ui/core/Typography"; import { makeStyles } from "@material-ui/core/styles"; import AuthForm from "./form/AuthForm"; const useStyles = makeStyles(theme => ({ paper: { marginTop: theme.spacing(8), display: "flex", flexDirection: "column", alignItems: "center" }, avatar: { margin: theme.spacing(1), backgroundColor: theme.palette.secondary.main }, link: { color: theme.palette.secondary.main } })); const SignUpView = () => { const classes = useStyles(); const { register, handleSubmit } = useForm(); const onSubmit = data => { console.log(data); }; return ( <div className={classes.paper}> <React.Fragment> <Avatar className={classes.avatar}> <PersonAddIcon /> </Avatar> <Typography component="h1" variant="h5"> Sign Up </Typography> <AuthForm register={register} handleSubmit={handleSubmit} onSubmit={onSubmit} /> </React.Fragment> </div> ); }; export default SignUpView;
onSubmitはまずはconsole.logするだけにしておきます。
また、ログイン画面にも流用するためフォーム部分は別のコンポーネントに分割しています。
components > content > form > AuthForm.jsを作成します。
import React from "react"; import TextField from "@material-ui/core/TextField"; import Button from "@material-ui/core/Button"; import { makeStyles } from "@material-ui/core/styles"; const useStyles = makeStyles(theme => ({ form: { width: "100%", // Fix IE 11 issue. marginTop: theme.spacing(1) }, submit: { margin: theme.spacing(3, 0, 2) } })); const AuthForm = props => { const classes = useStyles(); const { handleSubmit, register, onSubmit } = props; return ( <form className={classes.form} onSubmit={handleSubmit(onSubmit)}> <TextField variant="outlined" margin="normal" required fullWidth id="email" label="Email Address" name="email" autoComplete="email" autoFocus inputRef={register} /> <TextField variant="outlined" margin="normal" required fullWidth name="password" label="Password" type="password" id="password" autoComplete="current-password" inputRef={register} /> <Button type="submit" fullWidth variant="contained" color="primary" className={classes.submit} > Submit </Button> </form> ); }; export default AuthForm;
フォーム画面についてはこれでOKです。
最後にcomponents > layout > Content.jsにRouteを追加します。
<Route path="/signup"> <Grid container justify="center" className={classes.root}> <Grid item maxwidth="xs"> <SignUpView /> </Grid> </Grid> </Route>
これでSign Up画面は完成です。
Routeで指定した通り"/signup"にアクセスすればユーザ登録画面が表示されます。
メールアドレスとパスワードを入力すればコンソールに表示されるはずです。確認してみて下さい。
ユーザ登録
では次にロジック部分を作っていきます。
actions > auth.jsを作成します。
ユーザ登録はFirebaseのドキュメントを参考にしつつ、https://identitytoolkit.googleapis.com/v1/accounts:signUp?key=[API_KEY]
このエンドポイントにemail, passwordの入ったデータでPOSTします。
import * as ACTION_TYPES from "../actions/actionTypes"; import axiosAuth from "../axios/axiosAuth"; import { apiKey } from "../utils/setting"; import { apiFailed } from "./index"; import { showBackdrop, showMessage } from "./uiState"; export const authenticate = data => async dispatch => { dispatch(showBackdrop()); // Sign Up try { data.returnSecureToken = true; const result = await axiosAuth.post("accounts:signUp?key=" + apiKey, data); if (result.status === 200) { console.log(result); dispatch(showMessage("ユーザ登録に成功しました。", "success")); // 成功のメッセージを表示 } else { // 認証に失敗した場合はエラーメッセージを表示する dispatch( apiFailed("ユーザ登録に失敗しました。もう一度やり直して下さい。") ); } } catch (error) { // 通信が失敗した場合もエラーメッセージを表示する dispatch(apiFailed("ユーザ登録に失敗しました。")); } };
通信に必要なAPI KeyはFirebaseコンソールで確認できます。
プロジェクト設定内の「ウェブAPIキー」の欄にあります。
これを変数として設定・もしくはファイルで保存しておいて下さい。
コードではsrc > utils > settings.jsで変数として設定しexportしています。
なおaxiosに関しては、認証用にもう一つインスタンスを生成します。
src > axios > axiosAuth.jsを作成し以下のように認証用のベースURLを指定しておきます。
import axios from "axios"; const instance = axios.create({ baseURL: "https://identitytoolkit.googleapis.com/v1/" }); export default instance;
認証の通信を行う際はこのaxiosインスタンスを使用します。
定義したauthenticateメソッドをSignUpViewコンポーネントのonSubmitでdispatchします。
// importやuseDispatchもすること const onSubmit = data => { dispatch(authenticate(data)); };
メールアドレスとパスワードを入力し、Submitすると実際にFirebaseにユーザ登録されます。
このようにコンソールに結果が表示されます。
ここでidTokenが上で説明したトークンになります。localIdがユーザID, expiresInは3600となっていますが、これはトークンの有効期限が3600秒(60分)ということを意味します。
refreshTokenはトークンを更新するためのもので、有効期限が切れた場合に新たなトークンを発行するために使います。
あとはこのトークンを保存する仕組みを作ります。
トークンの保存
トークンの保存はブラウザのlocalStorageを使用します。
トークンの保存などに使うメソッドをいくつか用意したので、
src > utils > tokenUtils.jsに以下のファイルを作成して下さい。
export const setItem = (key, value) => { // localStorage以外の保存方法に切り替えるようにするため // localStorage, sessionStorage, Cookie, ... localStorage.setItem(key, value); }; export const getItem = key => { return localStorage.getItem(key); }; const removeItem = key => { localStorage.removeItem(key); }; /** * トークン・ID・有効期限を保存する * * @param {String} token - 認証トークン * @param {String} id - ユーザID * @param {Number} expiresIn - 期限切れまでの時間(単位は秒) */ export const saveAuthTokens = (token, id, expiresIn) => { setItem("token", token); setItem("userId", id); setItem("exp", new Date(new Date().getTime() + expiresIn * 1000)); }; /** * 保存されたトークン・ID・有効期限を削除する */ export const removeAuthTokens = () => { removeItem("token"); removeItem("userId"); removeItem("exp"); }; /** * 保存されているトークンの有効期限が切れているか確認する * * @returns {Boolean} - 有効期限が切れていればtrueを返す */ export const isExpired = () => { const expirationDate = getItem("exp"); if (new Date(expirationDate) <= new Date()) { // トークンの期限切れ return true; } else { return false; } }; export const getUserId = () => { return getItem("userId"); };
ポイント
上でも言及しましたが、トークンの保存方法については当サイトの別の記事でまとめましたので割愛します。
localStorageの他にもブラウザを閉じたときに、保存したものが削除されるsessionStorageなど保存方法はいくつかあります。
好みに応じて書き換えられるように、setItem
やgetItem
などのラッパーメソッドを使っているので、適宜変更して下さい。
tokenUtilsに作ったsaveAuthTokensメソッドでトークンを保存できます。
actions > auth.jsで通信後にconsole.logしていた箇所を以下のように書き換えます。
saveAuthTokens( result.data.idToken, result.data.localId, result.data.expiresIn );
注意ポイント
FirebaseのrefreshTokenは有効期限が設定されていないようで、ユーザが削除されたりしない限りは使い続けられるようです。これを保存するとセキュリティ上良くないので、今回はこのrefreshTokenは使用しません。
これにてユーザ登録をし、トークンを保存するところまで出来ました!
次回はログイン機能を実装していきます。
===
続きはこちらから↓
React + Firebaseのミニアプリを作る【認証編2】
前回に続きReact + Firebaseのミニアプリを作っていきます。 前回はユーザ登録を実装しました。 今回はログイン機能を作っていきます。 Contentsログイン処 ...
続きを見る