Webプログラミング

React + Firebaseのミニアプリを作る【認証編2】

前回に続きReact + Firebaseのミニアプリを作っていきます。

前回はユーザ登録を実装しました。

React + Firebaseのミニアプリを作る【認証編】

前回に続きReact+Firebaseでミニアプリを作っていきます。前回の投稿はこちらから。 どんなアプリを作るかは【計画編】を見て下さい。   今回はFirebaseでの認証を実装していき ...

続きを見る

 

今回はログイン機能を作っていきます。

 

ログイン処理の全体像

ログイン処理(Sign In)を作っていきますが、まずはやることを把握しておきましょう。

やることとしては、

  1. 登録したメールアドレス・パスワードで認証(ログイン)できる
  2. ログインしていなければログイン画面へリダイレクトさせる
  3. ログインしていればユーザ情報を使ってデータを取得・保存する
  4. リロード後もトークンがあればログイン状態を保つ

これらを上から順に実装していきます。
(最後にログアウトも実装します。)

 

登録したメールアドレス・パスワードで認証(ログイン)

ログイン状態の管理

一口に「ログイン」といっても、まずはログイン状態をどのように判別するかを決める必要があります。

 

今回はログイン状態に関してはisAuthという変数を定義し、ログイン状態を管理します。

ログイン(Sign in)に成功すればisAuthをtrueにログアウト(Sign out)すればisAuthをfalseにします。

reducers > auth.js

import * as ACTION_TYPES from "../actions/actionTypes";

const initialState = {
  isAuth: false
};

const auth = (state = initialState, action) => {
  switch (action.type) {
    case ACTION_TYPES.SIGN_IN:
      return {
        ...state,
        isAuth: true
      };
    case ACTION_TYPES.SIGN_OUT:
      return {
        ...state,
        isAuth: false
      };
    default:
      return state;
  }
};

export default auth;

例によってactionTypes.jsも追加しておきます。

// Auth
export const SIGN_IN = "SIGN_IN";
export const SIGN_OUT = "SIGN_OUT";

 

作成したauth.jsをcombineReducersに渡すことを忘れずに!

import auth from "./auth";

const rootReducer = combineReducers({ subjects, uiState, auth });

 

ログイン処理の実装

次に実際のログイン処理を実装していきます。

ログイン処理の流れはユーザ登録とほぼ同じで、以下のようになります。

  1. メールアドレス・パスワードを入力
  2. POSTメソッドで通信
  3. 返ってきたトークン・ユーザID・有効期限を保存
  4. ログイン状態(isAuth)をtrueにする

異なるのは通信先(エンドポイント)だけなので、ユーザ登録のロジックをほぼ流用出来ます。

以下のコードは一例です。

isSignUpという変数でユーザ登録かどうかを判別し、通信先を切り替えています。

export const authenticate = (isSignUp, data) => async dispatch => {
  dispatch(showBackdrop());
  let param;
  let messageString;
  if (isSignUp) {
    // Sign Up
    // ユーザ新規登録の通信先
    param = "accounts:signUp";
    messageString = "ユーザ登録";
  } else {
    // Sign In
    // ユーザログインの通信先
    param = "accounts:signInWithPassword";
    messageString = "サインイン";
  }
  try {
    data.returnSecureToken = true;
    const result = await axiosAuth.post(param + "?key=" + apiKey, data);
    if (result.status === 200) {
      // 認証に成功した場合はトークンを保存する
      saveAuthTokens(
        result.data.idToken,
        result.data.localId,
        result.data.expiresIn
      );
      dispatch(signinSuccess()); // アクションの生成
      dispatch(showMessage(messageString + "に成功しました。", "success")); // 成功のメッセージを表示
    } else {
      // 認証に失敗した場合はエラーメッセージを表示する
      dispatch(
        apiFailed(messageString + "に失敗しました。もう一度やり直して下さい。")
      );
    }
  } catch (error) {
    // 通信が失敗した場合もエラーメッセージを表示する
    dispatch(apiFailed(messageString + "に失敗しました。"));
  }
};

上述の通り、ログインに成功した場合はisAuthをtrueにするaction creatorをdispatchします。

const signinSuccess = () => {
  return {
    type: ACTION_TYPES.SIGN_IN
  };
};

 

ついでにログアウト処理も実装しておきます。

保存したトークンを削除し、SIGN_OUTのアクションを作成します。

actions > auth.js

export const signout = () => {
  removeAuthTokens();
  return {
    type: ACTION_TYPES.SIGN_OUT
  };
};

 

これでロジック部分は出来ました。あとはコンポーネントを作成していきます。

 

ログイン画面の実装

最後にログイン画面の実装、つまりコンポーネントを作成していきます。

コンポーネントに関してもユーザ登録とほぼ同じように作ることが出来ます。

onSubmitでauthenticateをdispatchする際にisSignUp引数を渡すところに注意して下さい。

import React from "react";
import { useDispatch } from "react-redux";
import { useForm } from "react-hook-form";
import Avatar from "@material-ui/core/Avatar";
import LockOutlinedIcon from "@material-ui/icons/LockOutlined";
import Typography from "@material-ui/core/Typography";
import { makeStyles } from "@material-ui/core/styles";
import AuthForm from "./form/AuthForm";
import { authenticate } from "../../actions/auth";

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 SignInView = () => {
  const classes = useStyles();
  const dispatch = useDispatch();
  const { register, handleSubmit } = useForm();
  const onSubmit = data => {
    dispatch(authenticate(false, data));  // Sign InなのでisSignUpはfalse
  };

  return (
    <div className={classes.paper}>
      <React.Fragment>
        <Avatar className={classes.avatar}>
          <LockOutlinedIcon />
        </Avatar>
        <Typography component="h1" variant="h5">
          Sign In
        </Typography>
        <AuthForm
          register={register}
          handleSubmit={handleSubmit}
          onSubmit={onSubmit}
        />
      </React.Fragment>
    </div>
  );
};

export default SignInView;

 

注意ポイント

action creatorのauthenticateメソッドでisSignUp引数を追加したため、ユーザ登録(SignUpView)の方でも修正が必要です。

Routeを追加することも忘れずに。

<Route path="/signin">
  <Grid container justify="center" className={classes.root}>
    <Grid item maxwidth="xs">
      <SignInView />
    </Grid>
  </Grid>
</Route>

 

このように表示されます。

 

実際にユーザ登録したメールアドレス・パスワードを入力してSubmitするとログイン処理が実行されます。

ChromeのDeveloper ToolのApplicationタブで確認出来ます。

左側のメニューのLocal Storageからlocalhost:3000のところを選ぶと以下のように保存されたトークンを見ることが出来ます。

これでトークンが正常に保存されていることが確認できました。

Redux DevToolsでStateを確認してみるとisAuthがtrueになっていることが確認できます。

 

これでログインが実装できました。

 

ログインしていなければログイン画面へリダイレクトさせる

ログイン状態によって見られるページ、見られないページを管理する必要がありますが、これはログイン状態(isAuth)によってReact RouterのSwitchを丸ごと変えてしまうのが簡単です。

(そのまま載せると長くなりすぎるので所々省略しています。)

// import文は長いので省略

const useStyles = makeStyles(theme => ({
  root: {
    marginTop: 5
  }
}));

const Content = props => {
  const classes = useStyles();
  const isAuth = useSelector(state => state.auth.isAuth);
  let route;
  if (isAuth) {
    route = (
      <Switch>
        ここに元々あったログイン時のRoute
        <Route exact path="/">
          <Grid container spacing={5} justify="center" className={classes.root}>
            <ListView />
          </Grid>
        </Route>
        <Redirect to="/" />
      </Switch>
    );
  } else {
    route = (
      <Switch>
        <Route path="/signin">
          <Grid container justify="center" className={classes.root}>
            <Grid item maxwidth="xs">
              <SignInView />
            </Grid>
          </Grid>
        </Route>
        <Route path="/signup">
          <Grid container justify="center" className={classes.root}>
            <Grid item maxwidth="xs">
              <SignUpView />
            </Grid>
          </Grid>
        </Route>
        <Redirect to="/signin" />
      </Switch>
    );
  }
  return (
    <Container>
      <Snackbar />
      {route}
    </Container>
  );
};
export default Content;

ポイント

Gridのせいで明らかに分かりづらくなっていますが、ここではご容赦下さい。もう少し整理されたコードを見たい方はこちらからダウンロードして下さい。

Switchの最後にRedirectを書くことで、リンクが一致しなければ"/signin"にリダイレクトするようになります。

ログインを実行してみると以下のように"/"へリダイレクトします。

 

ただし現在の状況ではリロードすると(トークンはあっても)再びログアウト状態になってしまいますし、データの取得や保存は出来ません。

  • ログインしていればユーザ情報を使ってデータを取得・保存する
  • リロード後もトークンがあればログイン状態を保つ
  • ログアウト

これらに関しては次回に対応していきます。

 

 

 

 

===

続きはこちらから↓

React + Firebaseのミニアプリを作る【認証編3】

前回に続きReact + Firebaseのミニアプリを作っていきます。 前回はログイン処理の実装途中でした。   今回も引き続きログインを実装していきます。   Contents ...

続きを見る

 

この記事は参考になりましたか?

下記のボタンよりアンケートにご協力お願いします。

-Webプログラミング
-, , ,

© 2020 エンジニアの本棚