【问题标题】:Call Azure AD protected API from React从 React 调用受 Azure AD 保护的 API
【发布时间】:2020-02-21 13:45:37
【问题描述】:

我有一个需要访问托管在 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 中从那里获取它。根据我上面的代码,我在哪里可以访问令牌并将其保存在本地存储中?

【问题讨论】:

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


    【解决方案1】:

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

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

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

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

    【讨论】:

    • 我在控制台中看到:令牌不在缓存范围内:api://{api_azure_client_id}/user_impersonation。然后它似乎卡在 Renew token for scope api://{api_azure_client_id}/user_impersonation 正在进行中。注册呼叫球。
    • @Munhu 你不是配置了同样的登录使用范围吗?
    • 是的,我做到了。我的配置如上。
    【解决方案2】:

    在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中从本地存储中获取token添加window.localStorage.getItem('accessToken');然后传递给axios header

    【讨论】:

      猜你喜欢
      • 2020-02-19
      • 2017-11-02
      • 2019-12-03
      • 2016-12-27
      • 1970-01-01
      • 2018-06-25
      • 1970-01-01
      • 1970-01-01
      • 2020-06-11
      相关资源
      最近更新 更多