フリーランス 技術調査ブログ

フリーランス/エンジニア Ruby Python Nodejs Vuejs React Dockerなどの調査技術調査の備忘録

ReactRouterで会員認証を実装したかった

はじめに

  • 下記のサンプルのようにログイン済みのユーザーのみ閲覧できるページを実装したかった。

reacttraining.com

  • 理想は下記のように実装したかった。
import React from "react";
import {
  BrowserRouter as Router,
  Switch,
  Route,
  Link,
  Redirect,
  useHistory,
  useLocation
} from "react-router-dom";

// This example has 3 pages: a public page, a protected
// page, and a login screen. In order to see the protected
// page, you must first login. Pretty standard stuff.
//
// First, visit the public page. Then, visit the protected
// page. You're not yet logged in, so you are redirected
// to the login page. After you login, you are redirected
// back to the protected page.
//
// Notice the URL change each time. If you click the back
// button at this point, would you expect to go back to the
// login page? No! You're already logged in. Try it out,
// and you'll see you go back to the page you visited
// just *before* logging in, the public page.

export default function AuthExample() {
  return (
    <Router>
      <div>
        <AuthButton />

        <ul>
          <li>
            <Link to="/public">Public Page</Link>
          </li>
          <li>
            <Link to="/protected">Protected Page</Link>
          </li>
        </ul>

        <Switch>
          <Route path="/public">
            <PublicPage />
          </Route>
          <Route path="/login">
            <LoginPage />
          </Route>
          <PrivateRoute path="/protected">
            <ProtectedPage />
          </PrivateRoute>
        </Switch>
      </div>
    </Router>
  );
}

const fakeAuth = {
  isAuthenticated: false,
  authenticate(cb) {
    fakeAuth.isAuthenticated = true;
    setTimeout(cb, 100); // fake async
  },
  signout(cb) {
    fakeAuth.isAuthenticated = false;
    setTimeout(cb, 100);
  }
};

function AuthButton() {
  let history = useHistory();

  return fakeAuth.isAuthenticated ? (
    <p>
      Welcome!{" "}
      <button
        onClick={() => {
          fakeAuth.signout(() => history.push("/"));
        }}
      >
        Sign out
      </button>
    </p>
  ) : (
    <p>You are not logged in.</p>
  );
}

// A wrapper for <Route> that redirects to the login
// screen if you're not yet authenticated.
function PrivateRoute({ children, ...rest }) {
  return (
    <Route
      {...rest}
      render={({ location }) =>
        fakeAuth.isAuthenticated ? (
          children
        ) : (
          <Redirect
            to={{
              pathname: "/login",
              state: { from: location }
            }}
          />
        )
      }
    />
  );
}

function PublicPage() {
  return <h3>Public</h3>;
}

function ProtectedPage() {
  return <h3>Protected</h3>;
}

function LoginPage() {
  let history = useHistory();
  let location = useLocation();

  let { from } = location.state || { from: { pathname: "/" } };
  let login = () => {
    fakeAuth.authenticate(() => {
      history.replace(from);
    });
  };

  return (
    <div>
      <p>You must log in to view the page at {from.pathname}</p>
      <button onClick={login}>Log in</button>
    </div>
  );
}
  • 結果的には下記のような実装になってしまった。

React側の実装

  • Auth.jsの内部で会員登録時に生成したトークンを渡して有効期限が過ぎていないかチェックする
  • 有効期限が切れていたら,dispatchでLOGOUT処理を呼び出してからログイン画面にリダイレクトする
import React,{ useState,useEffect,useContext } from 'react'
import axios from 'axios'
import AppContext from '../contexts/AppContext'

const Auth = ({ children, ...rest }) =>{
  const {state, dispatch } = useContext(AppContext)

  useEffect(() => {
    const authCheck = async () => {
      await axios.post('https://www.hogehoge.com/backend/api/auth',{
        headers: {Authorization: `Bearer ${state.user.token}`}
      }).then(res =>{
        console.log(res)
        if (res.data.auth == false) {
          dispatch({ type: LOGOUT })    
          window.location.href = '/login'
        } 
      })
    }
    authCheck()
  },[])
  return(<></>)
}

export default Auth
    <Router>
        <Switch>
        (省略)                  
          <Route path="/map">
            <Auth />
            <Location />
          </Route>
         (省略)
        </Switch>
      </Container>
    </Router >

express

  • authリクエスト認証を受けてトークンの有効期限をチェックした結果をreactに返す
router.post('/auth', async (req, res, next) => {
  try {
    jwt.verify(req.body.headers.Authorization, config.secret_key);
    res.json({auth: true})
  } catch(err) {
    res.json({auth: false})
  } 
});