从React调用受Azure AD保护的API

时间:2020-02-21 13:45:37

标签: reactjs axios azure-active-directory react-hooks msal

我有一个React Web应用程序,该应用程序需要访问Azure中托管的ASP.NET Core API。这两个应用程序均受Azure AD保护。我已经在Azure AD应用程序注册中分别注册了这些应用程序。我已经公开了给我这个范围的API:api:// {api_azure_client_id} / user_impersonation-此范围允许用户和管理员双方同意。我分别注册了react应用,并授予它访问公开的API范围的权限。

我正在使用MSAL来获取令牌。我可以成功签名,但是我不知道如何获取access_token来调用API。我正在关注the azure ad samples for react here。使用钩子的决定是受this pull request on the sample.

的启发

MSAL配置:

export const GRAPH_SCOPES = {
 OPENID: "openid",
 PROFILE: "profile",
 USER_READ: "User.Read",
 API_CALL: "api://{api_azure_client_id}/user_impersonation"
};

       export const GRAPH_ENDPOINTS = {
        ME: "https://graph.microsoft.com/v1.0/me"
      };

   export const GRAPH_REQUESTS = {
     LOGIN: {
      scopes: [
        GRAPH_SCOPES.OPENID,
        GRAPH_SCOPES.PROFILE,
        GRAPH_SCOPES.USER_READ,
        GRAPH_SCOPES.API_CALL
      ]
    }
  };

    export const msalApp = new UserAgentApplication({
      auth: {
        clientId: "react_app_azure_client_id",
        authority: "https://login.microsoftonline.com/common",
        validateAuthority: true,
        postLogoutRedirectUri: "http://localhost:3000",
        navigateToLoginRequestUrl: false
      },
      cache: {
        cacheLocation: "localStorage",
        storeAuthStateInCookie: isIE()
      }
    });

useAuthProvider挂钩:

    import { useState, useEffect } from "react";
    import { msalApp, requiresInteraction, fetchMsGraph, isIE, GRAPH_ENDPOINTS, GRAPH_REQUESTS
           } from "./../utils/auth-util";

     // If you support IE, our recommendation is that you sign-in using Redirect APIs
    const useRedirectFlow = isIE();
    // const useRedirectFlow = true;

    export default function useAuthProvider() {
     const [account, setAccount] = useState(null);
     const [accessToken, setAccessToken] = useState(null);
     const [error, setError] = useState(null);
     const [graphProfile, setGraphProfile] = useState(null);

      useEffect(() => {
        async function doAuth() {
         msalApp.handleRedirectCallback(error => {
           if (error) {
      const errorMessage = error.errorMessage
        ? error.errorMessage
        : "Unable to acquire access token.";
      // setState works as long as navigateToLoginRequestUrl: false
      setError(errorMessage);
    }
  });

  const account = msalApp.getAccount();

  setAccount(account);

  if (account) {
    const tokenResponse = await acquireToken(
      GRAPH_REQUESTS.LOGIN,
      useRedirectFlow
    );

    if (tokenResponse) {
      setAccessToken(tokenResponse.accessToken);
      const graphProfile = await fetchMsGraph(
        GRAPH_ENDPOINTS.ME,
        tokenResponse.accessToken
      ).catch(() => {
        setError("Unable to fetch Graph profile.");
      });

      if (graphProfile) {
        setGraphProfile(graphProfile);
      }
    }
  }
}

   doAuth();
   }, []);

     async function acquireToken(request, redirect) {
       return msalApp.acquireTokenSilent(request).catch(error => {
         // Call acquireTokenPopup (popup window) in case of acquireTokenSilent failure
         // due to consent or interaction required ONLY
         if (requiresInteraction(error.errorCode)) {
         return redirect ? msalApp.acquireTokenRedirect(request) : 
                msalApp.acquireTokenPopup(request);
         } else {
          console.error("Non-interactive error:", error.errorCode);
        }
      });
    }

      async function onSignIn(redirect) {
       if (redirect) {
         return msalApp.loginRedirect(GRAPH_REQUESTS.LOGIN);
       }

const loginResponse = await msalApp
  .loginPopup(GRAPH_REQUESTS.LOGIN)
  .catch(error => {
    setError(error.message);
  });

if (loginResponse) {
  setAccount(loginResponse.account);
  setError(null);

  const tokenResponse = await acquireToken(GRAPH_REQUESTS.LOGIN).catch(
    error => {
      setError(error.message);
    }
  );

  if (tokenResponse) {
    setAccessToken(tokenResponse.accessToken);
    const graphProfile = await fetchMsGraph(
      GRAPH_ENDPOINTS.ME,
      tokenResponse.accessToken
    ).catch(() => {
      setError("Unable to fetch Graph profile.");
    });

        if (graphProfile) {
           setGraphProfile(graphProfile);
          }
        }
      }
    }

     function onSignOut() {
        return msalApp.logout();
     }

     return {
       account,
       accessToken,
       error,
       graphProfile,
       onSignIn: () => onSignIn(useRedirectFlow),
       onSignOut: () => onSignOut()
     };
    }

我创建了一个httpService,它只是axios的包装。我正在使用此服务来调用api,httpService的配置如下,这是我需要访问令牌的地方。我在下面尝试过,但是我只能在函数体内调用钩子。

import axios from "axios";
import useAuthProvider from "./useAuthProvider";

   axios.interceptors.request.use(
     config => {
     const { accessToken } = useAuthProvider();

   if (accessToken) {
      config.headers["Authorization"] = "Bearer " + accessToken;
   }        
      return config;
 },
   error => {
     Promise.reject(error);
   }
  );

   export default {
      get: axios.get,
      post: axios.post,
      put: axios.put,
      delete: axios.delete
   };

如果用户未登录,我想显示一个带有“登录”按钮的组件,该按钮提示用户输入凭据。我正在App组件中执行此操作,并且它运行正常。

App.js

   import React from "react";
   import useAuthProvider from "./services/useAuthProvider";
   import NavBar from "./components/navigation/navBar";
   import "./App.css";
   import Welcome from "./components/welcome";

   function App() {
    const { account, accessToken, onSignIn } = useAuthProvider();

    if (account === null)
      return <Welcome authButtonMethod={onSignIn.bind(this)} />;
    return <NavBar />;
  }

    export default App;

我可以将令牌存储在本地存储中,然后在httpService中从那里获取它。根据上面的代码,在哪里可以访问令牌并将其保存在本地存储中?

2 个答案:

答案 0 :(得分:0)

我可以成功签名,但是我不知道如何获得 access_token调用API。

您可以成功签名,您已经在本地存储中有一个缓存的令牌(cacheLocation:“ localStorage”)。如果令牌尚未过期,则可以静默获取。

var accessToken = await this.userAgentApplication.acquireTokenSilent({
        scopes: config.scopes
      });

如果您要访问自己的webapi,则范围应该类似于api://{api_azure_client_id}/user_impersonation

答案 1 :(得分:0)

在Sign方法中,您可以通过添加window.localStorage.setItem('accessToken',tokenResponse.accessToken);

将访问令牌保存到本地存储中。

async function onSignIn(redirect) {
       if (redirect) {
         return msalApp.loginRedirect(GRAPH_REQUESTS.LOGIN);
       }

const loginResponse = await msalApp
  .loginPopup(GRAPH_REQUESTS.LOGIN)
  .catch(error => {
    setError(error.message);
  });

if (loginResponse) {
  setAccount(loginResponse.account);
  setError(null);

  const tokenResponse = await acquireToken(GRAPH_REQUESTS.LOGIN).catch(
    error => {
      setError(error.message);
    }
  );

  if (tokenResponse) {
    setAccessToken(tokenResponse.accessToken);
    window.localStorage.setItem('accessToken',tokenResponse.accessToken);
    const graphProfile = await fetchMsGraph(
      GRAPH_ENDPOINTS.ME,
      tokenResponse.accessToken
    ).catch(() => {
      setError("Unable to fetch Graph profile.");
    });

        if (graphProfile) {
           setGraphProfile(graphProfile);
          }
        }
      }
    }

在httpService中从本地存储中获取令牌,然后添加window.localStorage.getItem('accessToken');然后将其传递给Axios标头