首页 > 解决方案 > 在客户端 React 中存储 MSAL Graph API 令牌并重用它直到过期

问题描述

我对 React 还很陌生。这个问题与将身份验证令牌存储在客户端并重用它直到到期有关。

我正在使用 MSAL 在我的 React 应用程序中进行身份验证。下面是我的代码。

import * as React from 'react';

import {AuthOptions, CacheOptions, SystemOptions} from './MsalConfig';
import { UserAgentApplication } from 'msal';

//export const authContext: AuthenticationContext = new AuthenticationContext(msalConfig);

export const msalInstance = new UserAgentApplication({
  auth: AuthOptions,
  cache: CacheOptions,
  system: SystemOptions
});

export const withAuth = function(WrappedComponent){
  return class Auth extends React.Component{
    constructor(props) {
      super(props);

      this.state = {
        authenticated: false,
        renewIframe: false,
        hasError: false,
        errorMessage: null,
        graphAPIToken: null
      };
    }

    componentWillMount() {
      this.checkAuthentication();
    }

    checkAuthentication = () => {
      if(this.state.graphAPIToken == null){
        // action to perform on authentication
        msalInstance.handleRedirectCallback(() => { // on success
          this.setState({
            authenticated: true
          });
        }, (authErr, accountState) => {  // on fail
          console.log("Error after graph API call " + authErr);

          this.setState({
            hasError: true,
            errorMessage: authErr.errorMessage
          });
        });

        // if we are inside renewal callback (hash contains access token), do nothing
        if (msalInstance.isCallback(window.location.hash)) {
          this.setState({
            renewIframe: true
          });
          return;
        }

        // not logged in, perform full page redirect
        if (!msalInstance.getAccount()) {
          msalInstance.loginRedirect({});
          return;
        } else {     // logged in, set authenticated state
          this.setState({
            authenticated: true
          }, () => {
            this.getAccessToken(msalInstance);
          });
        }
      }
    }

    getAccessToken = (msalInstance) => {
      console.log(msalInstance);
      // if the user is already logged in you can acquire a token
      if (msalInstance.getAccount()) {
          var tokenRequest = {
              scopes: ["user.read"]
          };
          msalInstance.acquireTokenSilent(tokenRequest)
              .then(response => {
                  // get access token from response
                  // response.accessToken
                  console.log(response.accessToken);
                  this.setState({
                    graphAPIToken: response.accessToken
                  });
                  //return response.accessToken;
              })
              .catch(err => {
                  // could also check if err instance of InteractionRequiredAuthError if you can import the class.
                  if (err.name === "InteractionRequiredAuthError") {
                      return msalInstance.acquireTokenPopup(tokenRequest)
                          .then(response => {
                              // get access token from response
                              // response.accessToken
                              console.log(response.accessToken);
                              this.setState({
                                graphAPIToken: response.accessToken
                              });
                              //return response.accessToken;
                          })
                          .catch(err => {
                              console.log("Error inside getAccessToken " + err);
                          });
                  }
                  console.log("Error inside getAccessToken " + err);
              });
      } else {
          // user is not logged in, you will need to log them in to acquire a token
          console.log("u aren't logged in");
      }
  }

  callGraphAPI = (token) => {
      //..
  }

    render(){
      if (this.state.renewIframe) {
        return <div>hidden renew iframe - not visible</div>;
      }

      if (this.state.authenticated) {
        return <WrappedComponent {...this.props} />;
      }

      if (this.state.hasError) {
        console.log("Login error: " + this.state.errorMessage);
      }

      console.log("Login error: " + this.state.errorMessage);
    }
  };
}

现在,我在我的 React 路由中使用它index.js

ReactDOM.render(<MuiThemeProvider theme={theme}>
        <Router>
            <Switch>
                <Route path="/" exact component={(withAuth)(Projects)}/>
                <Route path="/signin" exact component={SignIn}/>
                <Route path="/projects" exact component={(withAuth)(Projects)}/>
                <Route path="/admin" exact component={(withAuth)(Admin)}/>
                <Route path="/projectstages/:projectNumber" exact component={(withAuth)(ProjectStages)}/>
                <Route path="/formspage" exact component={(withAuth)(FormsPage)}/>
                <Route path="/users" exact component={(withAuth)(UserManagement)}/>
            </Switch>
        </Router>
    </MuiThemeProvider>, document.getElementById('root'));

每当我访问任何 URL 时,都会调用componentWillMountof组件来检查身份验证。withAuth如何确保令牌只生成一次并存储在缓存中并重复使用直到过期?另外,我不确定我是否以正确的方式实施了身份验证。请问有什么帮助吗?

标签: javascriptreactjsazure-active-directoryazure-ad-graph-apimsal

解决方案


您不必担心缓存令牌和更新它,MSAL 会为您完成这一切。

(事实上​​,如果您使用 MSAL,我特别建议您不要进行自己的令牌缓存。您可能会意外引入自己的安全漏洞。)

文档中所述,每次您要发出 API 请求时,都应该调用acquireTokenSilent. 将发生以下三种情况之一:

  1. 如果 MSAL 在其自己的缓存中具有与您提供的参数匹配的现有令牌,则 MSAL 将立即提供该令牌。
  2. 如果需要刷新令牌(例如,因为缓存的访问令牌已过期,或者因为您需要不同 API 的访问令牌),MSAL 将尝试执行静默令牌刷新。如果此操作以静默方式成功,MSAL 将提供新的访问令牌。
  3. 如果 MSAL 没有缓存的有效访问令牌,并且 MSAL 无法静默刷新它(例如,用户尚未登录、他们已注销或用户需要同意新权限),则 MSAL 将让您知道,您可以根据需要处理这种情况(例如acquireTokenPopup,使用弹出窗口或acquireTokenRedirect使用重定向)。

推荐阅读