使用API​​ React Hook防止多次令牌刷新

时间:2020-03-13 11:22:27

标签: reactjs jwt

我有一个名为useAPICall的API挂钩,该挂钩具有回调call。此回调检查存储在名为auth的reactn变量中的令牌是否已过期,并在必要时刷新它,然后调用fetch函数。

我在组件中这样称呼它:

const [api] = useAPICall();

useEffect(() => {
    api.call('/api/settings/mine/').then(data => {
        // do stuff here
    });
}, []);

它确实起作用。它通过身份验证流程并调用API。但是,如果我有useAPICall个组件,它们都试图同时调用API(例如冷页面加载),那么它的每个实例都会调用refresh令牌方法,因为它已过期。

身份验证信息(访问/刷新令牌)存储在如下的reactn全局变量auth中,位于useAPICall.js钩子内

import React, {useCallback, useContext, useEffect, useMemo, useState} from 'react';
import {useDispatch, useGlobal} from 'reactn';


export function useAPICall() {
    const [auth, setAuth] = useGlobal('auth');
    const authRefreshSuccess = useDispatch('authRefreshSuccess');

    async function refreshToken() {
        console.log('Refreshing access token...');
        const authResponse = await fetch('/api/auth/token/refresh/', {
            method: 'POST',
            credentials: 'same-origin',
            body: JSON.stringify({refresh: auth.refresh.token}),
            headers: {
                'Content-Type': 'application/json',
            },
        });
        if (authResponse.ok) {
            const authToken = await authResponse.json();
            await authRefreshSuccess(authToken);
            return authToken.access;
        }
    }

    function isTokenExpired() {
        if (localAuth.access)
            return auth.access.exp <= Math.floor(Date.now() / 1000);
        else
            return false;
    }

    const call = useCallback(async (endpoint, options={headers: {}}) => {
        console.log('performing api call');
        token = undefined;

        if (isTokenExpired())
            token = await refreshToken();
        else
            token = localAuth.access.token;

        const res = await fetch(endpoint, {
            ...options,
            headers: {
                ...options.headers,
                'Authorization': `Bearer ${token}`,
            }
        });
        if (!res.ok)
            throw await res.json();
        return res.json();

    }, []);

    const anonCall = useCallback(async (endpoint, options={}}) => {
        const res = await fetch(endpoint, options);
        if (!res.ok)
            throw await res.json();
        return res.json();

    }, []);


    const api = useMemo(
        () => ({
            call,
            anonCall,
        }),
        [call, anonCall,]
    );

    return [api]
}

如何防止它们多次触发刷新方法?

如果有一种更好的方法(没有redux)具有通用的API流(任何API调用都将首先检查访问令牌并在必要时刷新),那么我愿意听。

1 个答案:

答案 0 :(得分:2)

我设法通过将promise存储在全局变量中来做到这一点。

let refreshPromise = null;
    
export function useAuthentication() {
    
      async function getBearer() {
    
        if (isExpired(jwt)) {
          if (refreshPromise == null) {
            refreshPromise = refresh().then((jwt) => {
              refreshPromise = null;
              return jwt;
            });
          }
          await refreshPromise;
        }
    
        let authData = getAuthData();
        if (authData && authData.accessToken) {
          return `Bearer ${authData.accessToken}`;
        }
        return null;
      }
    
      const AuthenticationService = {
        getBearer,
        ...
      };
    
     return AuthenticationService;
}

希望这会有所帮助!