如何在客户端PrivateRoute上授权jwt令牌?

时间:2019-07-05 09:34:04

标签: reactjs async-await react-router jwt authorization

我正在尝试向后端的授权端点发送一个简单的GET请求,以验证成功验证(登录)后在localStorage中保存的用户jwt令牌。我已经设置了一个辅助函数(请参见下文),该函数将在标头中带有jwt令牌的情况下向服务器发出GET请求,如果接收到状态200,则返回true(已授权)。

此辅助函数在我的PrivateRoute HOC中调用,如果为true,则返回私有组件;如果为false,则重定向到Login。

基本上,如果我只是解码令牌并验证令牌是否存在,因此用户已经登录,我就可以使受保护的路由正常工作。但是,这并不是针对服务器jwt verify来检查令牌,因此并不安全。我不确定是否需要在任何地方实现异步代码?我知道验证步骤必须在后端执行,前端应该只发送标头中带有令牌的API请求,但我似乎无法通过私有路由来完成这项工作。

我还尝试过在主App组件上将helper函数作为useEffect挂钩运行,并根据helper函数是否返回授权来更新“ loggedIn”状态变量,然后可以将其作为道具传递给其他组件。我已经完成了此操作,以使导航栏组件根据“ loggedIn”状态是true还是false来显示“ SIGN IN / REGISTER”或“ SIGN OUT”。

服务器端验证令牌功能:

const jwt = require('jsonwebtoken');

module.exports = function (req, res, next) {
    const token = req.header('auth-token');
    if(!token) return res.status(401).send('Access Denied');

    try {
        const decoded = jwt.verify(token, process.env.TOKEN_SECRET);
        req.user = decoded;
        next();
    } catch (err) {
        res.status(400).send('Invalid Token');
    }
}

辅助功能:

import React from 'react';
    import { Redirect } from 'react-router-dom';
    import axios from 'axios';

    const checkAuth = {
        isAuthorised: false,
        userID: void (0),

        authorise: function () {

            const token = window.localStorage.getItem("access_token");
            if (!token) {
                console.log('Invalid Token');
                window.localStorage.setItem("access_token", null);
            }

            const response = axios({
                method: 'get',
                url: 'http://localhost:3001/api/verify',
                headers: { 'auth-token': token }
            })
            const data = response.data;
            if (response.status === 200) {
                this.isAuthorised = true;
                this.userID = data._id;
                return this.isAuthorised;
            }

            return this.isAuthorised;
        },
        logout: function () {
            window.localStorage.setItem("access_token", null);
            this.isAuthorised = false;
            return <Redirect to="/" />
        },
    }

    export default checkAuth;

HOC私人路线:

    import React from 'react';
    import { Redirect, Route } from 'react-router-dom';
    import checkAuth from './helper';

    const PrivateRoute = ({ component: Component, path, ...rest }) => {

    return (
        <Route
            path={path}
            {...rest}
            render={props =>
                checkAuth.authorise()
                    ? <Component {...props} />
                    : <Redirect to={{ pathname: "/login" }} />
            }
        />
    );
        }
    export default PrivateRoute;

应用程序组件(隐藏导入):

const App = () => {
  const [loggedIn, setLoggedIn] = useState(false);
  const [user, setUser] = useState();

  const verify = () => {
    if (checkAuth.authorise()) {
      setLoggedIn(true);
      setUser(checkAuth.userID);
      console.log('logged in');
      return true;
    } else {
      console.log('not logged in');
      return false;
    }
  }

  useEffect(() => {
    verify()
  }, [loggedIn]);

  return (
    <div>
      <NavBar loggedIn={loggedIn} user={user} />
      <Router>
        <Switch>
          <Route exact path="/" component={Home} />
          <Route path="/story" component={Story} />
          <Route path="/prologue" component={Prologue} />
          <PrivateRoute path="/chapters" component={Chapters} />
          <PrivateRoute path="/forum" component={Forum} />
          <PrivateRoute path="/characters" component={Characters} />
          <PrivateRoute path="/characters/:id" component={CharacterSheet} />
          <Route path="/login" component={Login} />
          <Route path="/register" component={Register} />
          <Route component={NoMatch} />
        </Switch>
      </Router>
    </div>
  );
}

export default App;

NavBar组件(仅相关部分):

const NavBar = ({ loggedIn, user }) => {
    return (
      ...
          {loggedIn ? (
                    <Nav>
                        <h1 style={{ paddingRight: '30px', color: 'black', fontSize: '32pt'}}>Welcome, {user}</h1>
                        <Nav.Link href="/" onClick={() => checkAuth.logout()}>Sign Out</Nav.Link>
                    </Nav>
                ) : (
                        <Nav>
                            <Nav.Link href="/login">Login</Nav.Link>
                            <Nav.Link href="/register">Register</Nav.Link>
                        </Nav>
                    )}
                ...
    )
}

export default NavBar;

我希望用户登录时,NavBar会更新,以显示基于useEffect钩子调用的授权帮助器功能而登录的用户。

我还希望当用户路由到其中一条受保护的路由(PrivateRoute)时,helper函数将运行并返回true(登录后)并随后呈现。

在辅助函数中使用以下代码代替API调用时,我得到了预期的结果(但是据我所知这不是正确的授权):

    authorise: function () {

        const token = window.localStorage.getItem("access_token");
        if (!token) {
            console.log('Invalid Token');
            window.localStorage.setItem("access_token", "");
        }

        try {
            const decoded = decode(token);
            console.log(decoded)
            if (decoded) {
                this.isAuthorised = true;
                this.userID = decoded._id;
                return this.isAuthorised;
            }
        }
        catch {
            console.log('invalid token')
        }

        return this.isAuthorised;
    },

目前,登录似乎无能为力-尽管已收到用户令牌并将其存储在LocalStorage中。

1 个答案:

答案 0 :(得分:0)

axios返回一个承诺,因此,基本上,您需要将验证代码更改为以下内容:

return axios({
    method: 'get',
    url: 'http://localhost:3001/api/verify',
    headers: { 'auth-token': token }
})
    .then(response => {
        const data = response.data;
        if (response.status === 200) {
            this.isAuthorised = true;
            this.userID = data._id;
        }

        return this.isAuthorised;
    });

然后将checkAuth.authorise()呼叫也作为承诺进行处理