从 React 调用受 Azure AD 保护的 API
Posted
技术标签:
【中文标题】从 React 调用受 Azure AD 保护的 API【英文标题】:Call Azure AD protected API from React 【发布时间】: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 中从那里获取它。根据我上面的代码,我在哪里可以访问令牌并将其保存在本地存储中?
【问题讨论】:
【参考方案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
【讨论】:
以上是关于从 React 调用受 Azure AD 保护的 API的主要内容,如果未能解决你的问题,请参考以下文章
从 PowerApp(或逻辑应用)调用受 AAD 保护的 Azure 函数
如何使用 Azure AD B2C 保护 Blazor Wasm 应用访问的 Azure 函数?