首页 文章

在Redux应用程序中提供OAuth访问令牌的策略

提问于
浏览
6

我有一个Redux应用程序和一个用作OAuth服务器的远程API . 根据典型例程,用户将其凭据交换为令牌,然后由应用程序使用该令牌来获取数据并在服务器上执行某些操作 . 此令牌存储在商店中,也存储在 sessionStorage 中 .

现在,有时访问令牌会过期,但由于已经收到刷新令牌,最好先尝试刷新,并且只有在出错时才签署用户 .

我完全理解签约部分,从技术上讲,只是简单地发送某个动作 . 但是如何简化令牌刷新例程呢?

我尝试过redux-saga,但它非常冗长 . 我确实必须部分复制依赖于远程API的每个操作的代码,以确保每个请求首先检查访问令牌以及它是否尚未过期,否则设法刷新它 .

我试图做的另一件事是一个中间件,它会期望某种类型的操作请求将远程API包装到Promise中 . 这种作品,但我很好奇是否有另一种方法可做 .

有没有人实现过这种(非常通用)的东西?任何想法如何自动化令牌刷新,不要因为增加代码量而生气?也许更高阶的组件?

1 回答

  • 11

    对于需要反复发生的代码以及需要无缝且通用的内容,中间件通常是可行的方法 . 它可以像添加两行代码一样简单,以便在创建存储时包含中间件,并编写一个简单的函数来为您处理令牌逻辑 .

    假设您将创建您的商店:

    import { createStore, applyMiddleware, compose } from 'redux';
    import rootReducer from './reducers';
    import { browserHistory } from 'react-router';
    import { routerMiddleware } from 'react-router-redux';
    import tokenMiddleware from './middleware/token';
    
    const finalCreateStore = compose(
        applyMiddleware(
          routerMiddleware(browserHistory),
          tokenMiddleware,
        ),
        window.devToolsExtension ? window.devToolsExtension() : f => f,
    )(createStore);
    

    然后你可以从初始状态的某个地方调用这个函数 .

    const store = finalCreateStore(rootReducer, initialState);
    

    这将使您能够对通过商店的所有操作执行某些操作 . 由于使用promises处理API调用的中间件很常见,因此有些人更喜欢为此目的重新使用它,并将两者捆绑在一起 .

    典型的中间件看起来像这样:

    export const tokenMiddleware = ({ dispatch, getState }) => next => action => {
        if (typeof action === 'function') { // pass along
            return action(dispatch, getState);
        }
    
        // so let's say you have a token that's about to expire
        // and you would like to refresh it, let's write so pseudo code
    
        const currentState = getState();
        const userObj = state.authentication.user;
    
        if (userObj.token && userObj.token.aboutToExpire) {
           const config = getSomeConfigs();
           // some date calculation based on expiry time that we set in configs
           const now = new Date();
           const refreshThreshold = config.token.refreshThreshold;
    
           if (aboutToExpireAndIsBelowThresholdToRefresh) {
               // refreshTheToken can be an action creator
               // from your auth module for example
               // it should probably be a thunk so that you can handle 
               // an api call and a promise within once you get the new token
               next(refreshTheToken(userObj, someOtherParams);
           }
        }
    
        ....
    
        return next(action);
    }
    

    您的刷新令牌thunk可能与此类似:

    function refreshToken(user, maybeSomeOtherParams) {
        const config = getSomeConfigs;
    
        return dispatch => {
            makeAPostCallWithParamsThatReturnsPromise
            .then(result => dispatch(saveNewToken({
                result,
                ...
            })))
            .catch(error => dispatch({
                type: uh_oh_token_refresh_failed_action_type,
                error,
            }));
     };
    

    您可能会选择的另一种方法是在更改路线时处理此问题 .

    让's say you would have a top level route somewhere for the routes that need authentication and a valid user to be present in the system. Let'称他们为 authenticated routes .

    您可以使用顶级路由包装这些 authenticated routes ,它定义了 onChange 处理函数 . 像这样的东西:

    <Route onChange={authEntry}>
        <Route ... /> // authenticated routes
        <Route ... />
    </Route>
    

    创建这些路径并设置商店时,一旦创建了商店,就可以将其绑定到名为 checkAuth 的此函数 .

    const authEntry = checkAuth.bind(null, store)
    

    另一种方法是将路径定义包装在一个函数中并将商店传递给它,然后你就可以访问它,但我发现它不像这样(个人偏好) .

    那现在_175506会做什么?

    像这样的东西:

    export function checkAuth (store, previous, next, replace, callback) {
        const currentUser = store.getState().auth.user
    
        // can possibly dispatch actions from here too 
        // store.dispatch(..).then(() => callback())..
        // so you could possibly refresh the token here using an API call
        // if it is about to expire
    
        // you can also check if the token did actually expire and/or 
        // there's no logged in user trying to access the route, so you can redirect
    
        if (!currentUser || !isLoggedIn(currentUser)) {
            replace('/yourLoginRouteHere')
        }
    
        callback() // pass it along
    }
    

    这些都应该足够通用,以便它们在集中位置为您提供可重用的代码 . 希望你会发现这些有用 .

相关问题