我无法理解在尝试使用 React 的 Micrsoft 身份验证库(@azure/msal-react)对用户进行身份验证时遇到的错误。我需要帮助理解为什么尝试使用 loginPopup 方法登录用户时失败。 代码:
import { useEffect, useState, useContext } from "react";
import { useNavigate } from "react-router-dom";
import { Link } from "react-router-dom";
import Card from "@mui/material/Card";
import MDBox from "components/MDBox";
import MDTypography from "components/MDTypography";
import MDButton from "components/MDButton";
import CoverLayout from "layouts/authentication/components/CoverLayout";
import { useMsal } from "@azure/msal-react";
import { loginRequest, groupId } from "authConfig";
import bgImage from "assets/images/btbbackground.jpg";
import { GlobalStateContext } from "GlobalStateContext";
import {userLoginResponse} from "./loginResponse";
function Basic() {
const navigate = useNavigate();
const { instance } = useMsal();
const [rememberMe, setRememberMe] = useState(false);
const { updateStateObj } = useContext(GlobalStateContext);
const handleSetRememberMe = () => setRememberMe(!rememberMe);
const handleSignIn = async () => {
try {
let loginResponse = await instance.loginPopup(loginRequest);
// let loginResponse = userLoginResponse;
console.log('loginResponse from handleSignIn is : ', loginResponse);
if (loginResponse) {
handleSuccessfulLogin(loginResponse);
}
} catch (error) {
console.error('Error during authentication', error);
}
};
const handleSuccessfulLogin = (loginResponse) => {
const group_list = loginResponse.idTokenClaims.groups || [];
console.log('Group list', group_list);
if (group_list.length > 0) {
const IsCallAssuranceAgent = group_list.includes(groupId.AICallAssuranceAgent);
const IsCallAssuranceMgr = group_list.includes(groupId.AICallAssuranceMgr);
const IsCallSummary = group_list.includes(groupId.AICallSummary);
const stateItem = {
agentId: loginResponse.account.name,
email: loginResponse.account.username,
AICallAssuranceAgent: IsCallAssuranceAgent,
AICallAssuranceMgr: IsCallAssuranceMgr,
AICallSummary: IsCallSummary,
groupId: group_list.join()
};
// Update global state
updateStateObj(stateItem);
// Navigate based on roles
if (IsCallSummary) {
navigate("/callsummary");
} else if (IsCallAssuranceAgent || IsCallAssuranceMgr) {
navigate("/callassurance");
}
} else {
navigate("/unauthorized");
}
};
useEffect(() => {
console.log('in instance use effect , ');
instance.handleRedirectPromise()
.then((response) => {
if (response) {
console.log('Login response from handleRedirectPromise', response);
handleSuccessfulLogin(response);
}
})
.catch((error) => {
console.error('Error handling redirect response', error);
});
}, [instance]);
AuthConfirm.js: 哈希_空_错误
错误详情:
authConfig.js:19 [Sun, 06 Oct 2024 17:36:03 GMT] : [019262e6-c629-7327-96d7-5a73bd4f352b] : [email protected] : Error - The request has returned to the redirectUri but a fragment is not present. It's likely that the fragment has been removed or the page has been redirected by code running on the redirectUri page.
loggerCallback @ authConfig.js:19
executeCallback @ Logger.ts:152
logMessage @ Logger.ts:136
error @ Logger.ts:160
NK @ ResponseHandler.ts:29
(anonymous) @ FunctionWrappers.ts:43
acquireTokenPopupAsync @ PopupClient.ts:279
await in acquireTokenPopupAsync
acquireToken @ PopupClient.ts:120
acquireTokenPopup @ StandardController.ts:795
loginPopup @ StandardController.ts:1869
loginPopup @ PublicClientApplication.ts:279
onClick @ index.js:25
qe @ react-dom.production.min.js:52
Ye @ react-dom.production.min.js:52
(anonymous) @ react-dom.production.min.js:53
Ir @ react-dom.production.min.js:100
Er @ react-dom.production.min.js:101
(anonymous) @ react-dom.production.min.js:113
De @ react-dom.production.min.js:292
(anonymous) @ react-dom.production.min.js:50
Nr @ react-dom.production.min.js:105
Xt @ react-dom.production.min.js:75
Jt @ react-dom.production.min.js:74
t.unstable_runWithPriority @ scheduler.production.min.js:18
Ko @ react-dom.production.min.js:122
_e @ react-dom.production.min.js:292
Qt @ react-dom.production.min.js:73
Show 25 more frames
Show less
authConfig.js:20 [Sun, 06 Oct 2024 17:36:03 GMT] : [] : @azure/[email protected] : Info - Emitting event: msal:loginFailure
authConfig.js:20 [Sun, 06 Oct 2024 17:36:03 GMT] : [] : @azure/[email protected] : Info - MsalProvider - msal:loginFailure results in setting inProgress from login to none
index.js:32 Error during authentication BrowserAuthError: hash_empty_error: Hash value cannot be processed because it is empty. Please verify that your redirectUri is not clearing the hash. For more visit: aka.ms/msaljs/browser-errors
at jW (BrowserAuthError.ts:359:12)
at NK (ResponseHandler.ts:32:19)
at FunctionWrappers.ts:43:28
at DK.acquireTokenPopupAsync (PopupClient.ts:279:34)
at async onClick (index.js:25:11)
有人可以检查并帮助我解决此错误吗?
我的代码需要一些建议来修复上述错误。
路由配置: 应用程序.js
import React, { useState, useEffect, useContext } from "react";
import { Routes, Route, Navigate, useLocation, useNavigate } from "react-router-dom";
import { ThemeProvider } from "@mui/material/styles";
import CssBaseline from "@mui/material/CssBaseline";
import Icon from "@mui/material/Icon";
import MDBox from "components/MDBox";
import Sidenav from "examples/Sidenav";
import Configurator from "examples/Configurator";
import theme from "assets/theme";
import themeDark from "assets/theme-dark";
import CallassuranceComponent from "layouts/callassurance";
import CallsummaryComponent from "layouts/callsummary";
import SignIn from "layouts/authentication/sign-in"; // Import the SignIn component
import { useMaterialUIController, setMiniSidenav, setOpenConfigurator } from "context";
import { PublicClientApplication, EventType, InteractionStatus } from "@azure/msal-browser";
import { MsalProvider, useIsAuthenticated, useMsal } from "@azure/msal-react";
import routes from "routes";
import { msalConfig } from "./authConfig";
// Images
import brandWhite from "assets/images/logos/BT_logo.png";
import brandDark from "assets/images/logos/BT_logo.png";
import { GlobalStateContext } from 'GlobalStateContext';
const pca = new PublicClientApplication(msalConfig);
function PrivateRoute({ children }) {
const { inProgress } = useMsal();
const isAuthenticated = useIsAuthenticated();
const { stateObj } = useContext(GlobalStateContext);
if (inProgress === InteractionStatus.Startup) {
// MSAL is still checking for cached tokens, so show a loading state
return <div>Loading...</div>;
}
// Check if the user is authenticated and stateObj is not null and contains some values
if (!isAuthenticated || !stateObj || Object.keys(stateObj).length === 0) {
// Redirect to sign-in page if not authenticated or stateObj is empty
return <Navigate to="/authentication/sign-in" />;
}
// If everything is valid, render the route's children
return children;
}
function App() {
const location = useLocation();
const navigate = useNavigate();
const { email } = location.state || {};
const [controller, dispatch] = useMaterialUIController();
const {
miniSidenav,
direction,
layout,
openConfigurator,
sidenavColor,
transparentSidenav,
whiteSidenav,
darkMode,
} = controller;
const [onMouseEnter, setOnMouseEnter] = useState(false);
const { pathname } = useLocation();
const [allowedPages, setAllowedPages] = useState(["sign-in", "sign-up", "report", "authentication/sign-in", "unauthorized"]);
const { instance, accounts, inProgress } = useMsal();
const isAuthenticated = useIsAuthenticated();
const { stateObj } = useContext(GlobalStateContext);
useEffect(() => {
const fetchAllowedPages = async () => {
const allRouteKeys = routes.map(route => route.key);
setAllowedPages(allRouteKeys);
};
fetchAllowedPages();
}, []);
useEffect(() => {
document.body.setAttribute("dir", direction);
}, [direction]);
useEffect(() => {
if (stateObj) {
const { AICallAssuranceAgent, AICallAssuranceMgr, AICallSummary } = stateObj;
var allRouteKeys = AICallSummary ? allowedPages : allowedPages.filter(route => route !== 'callsummary');
allRouteKeys = AICallAssuranceAgent || AICallAssuranceMgr ? allRouteKeys : allRouteKeys.filter(route => route !== 'callassurance');
// Check if allowedPages needs to be updated
if (JSON.stringify(allRouteKeys) !== JSON.stringify(allowedPages)) {
setAllowedPages(allRouteKeys);
}
}
document.documentElement.scrollTop = 0;
document.scrollingElement.scrollTop = 0;
}, [stateObj, allowedPages]);
useEffect(() => {
navigate(location.pathname);
}, [inProgress, accounts, navigate, location.pathname]);
useEffect(() => {
const handleMsalEvent = (event) => {
if (event.eventType === EventType.LOGIN_SUCCESS) {
instance.setActiveAccount(event.payload.account);
}
};
const callbackId = instance.addEventCallback(handleMsalEvent);
return () => {
if (callbackId) {
instance.removeEventCallback(callbackId);
}
};
}, [instance]);
const handleOnMouseEnter = () => {
if (miniSidenav && !onMouseEnter) {
setMiniSidenav(dispatch, false);
setOnMouseEnter(true);
}
};
const handleOnMouseLeave = () => {
if (onMouseEnter) {
setMiniSidenav(dispatch, true);
setOnMouseEnter(false);
}
};
const handleConfiguratorOpen = () => setOpenConfigurator(dispatch, !openConfigurator);
const filteredRoutes = routes.filter(route => allowedPages.includes(route.key));
const configsButton = (
<MDBox
display="flex"
justifyContent="center"
alignItems="center"
width="3.25rem"
height="3.25rem"
bgColor="white"
shadow="sm"
borderRadius="50%"
position="fixed"
right="2rem"
bottom="2rem"
zIndex={99}
color="red"
sx={{ cursor: "pointer" }}
onClick={handleConfiguratorOpen}
>
<Icon fontSize="small" color="inherit">
settings
</Icon>
</MDBox>
);
return (
<MsalProvider instance={pca}>
<ThemeProvider theme={darkMode ? themeDark : theme}>
<CssBaseline />
{layout === "dashboard" && (
<>
<Sidenav
allowedpages={allowedPages}
color={sidenavColor}
brand={(transparentSidenav && !darkMode) || whiteSidenav ? brandDark : brandWhite}
brandName="AI For Agents"
routes={filteredRoutes}
onMouseEnter={handleOnMouseEnter}
onMouseLeave={handleOnMouseLeave}
/>
<Configurator />
</>
)}
{layout === "vr" && <Configurator />}
<Routes>
<Route path="/authentication/sign-in" element={<SignIn />} />
{filteredRoutes.map((route) => (
<Route
exact
path={route.route}
element={
<PrivateRoute>
{route.component}
</PrivateRoute>
}
key={route.key}
/>
))}
<Route path="*" element={<Navigate to="/callsummary" />} />
</Routes>
</ThemeProvider>
</MsalProvider>
);
}
export default App;
我在以下 React 应用程序中使用 Microsoft 身份验证库 (@azure/msal-react) 成功对用户进行了身份验证。
这是来自 GitHub 存储库的完整代码。
src/App.js :
import React, { useEffect, useState, useContext } from "react";
import { Routes, Route, Navigate, useLocation, useNavigate } from "react-router-dom";
import { ThemeProvider } from "@mui/material/styles";
import CssBaseline from "@mui/material/CssBaseline";
import Icon from "@mui/material/Icon";
import MDBox from "./components/UI/MDBox";
import theme from "./styles/theme";
import themeDark from "./styles/themeDark";
import SignIn from "./components/Auth/SignIn";
import PrivateRoute from "./components/Layout/PrivateRoute";
import routes from "./routes/routes";
import { msalConfig } from "./config/authConfig";
import { MsalProvider, useMsal, useIsAuthenticated } from "@azure/msal-react";
import { PublicClientApplication, EventType } from "@azure/msal-browser";
import { GlobalStateProvider, GlobalStateContext } from "./contexts/GlobalStateContext";
const pca = new PublicClientApplication(msalConfig);
function AppContent() {
const location = useLocation();
const navigate = useNavigate();
const [controller, setController] = useState({
miniSidenav: false,
direction: "ltr",
layout: "dashboard",
openConfigurator: false,
sidenavColor: "blue",
transparentSidenav: false,
whiteSidenav: false,
darkMode: false,
});
const {
miniSidenav,
direction,
layout,
openConfigurator,
sidenavColor,
transparentSidenav,
whiteSidenav,
darkMode,
} = controller;
const [onMouseEnter, setOnMouseEnter] = useState(false);
const [allowedPages, setAllowedPages] = useState(["sign-in", "sign-up", "report", "authentication/sign-in", "unauthorized"]);
const { instance, accounts, inProgress } = useMsal();
const isAuthenticated = useIsAuthenticated();
const { stateObj } = useContext(GlobalStateContext);
useEffect(() => {
const fetchAllowedPages = async () => {
const allRouteKeys = routes.map((route) => route.key);
setAllowedPages(allRouteKeys);
};
fetchAllowedPages();
}, []);
useEffect(() => {
document.body.setAttribute("dir", direction);
}, [direction]);
useEffect(() => {
if (stateObj) {
const { AICallAssuranceAgent, AICallAssuranceMgr, AICallSummary } = stateObj;
let allRouteKeys = AICallSummary ? allowedPages : allowedPages.filter((route) => route !== "callsummary");
allRouteKeys = AICallAssuranceAgent || AICallAssuranceMgr ? allRouteKeys : allRouteKeys.filter((route) => route !== "callassurance");
if (JSON.stringify(allRouteKeys) !== JSON.stringify(allowedPages)) {
setAllowedPages(allRouteKeys);
}
}
document.documentElement.scrollTop = 0;
document.scrollingElement.scrollTop = 0;
}, [stateObj, allowedPages]);
useEffect(() => {
navigate(location.pathname);
}, [inProgress, accounts, navigate, location.pathname]);
useEffect(() => {
const handleMsalEvent = (event) => {
if (event.eventType === EventType.LOGIN_SUCCESS) {
instance.setActiveAccount(event.payload.account);
}
};
const callbackId = instance.addEventCallback(handleMsalEvent);
return () => {
if (callbackId) {
instance.removeEventCallback(callbackId);
}
};
}, [instance]);
const handleOnMouseEnter = () => {
if (miniSidenav && !onMouseEnter) {
setController((prev) => ({ ...prev, miniSidenav: false }));
setOnMouseEnter(true);
}
};
const handleOnMouseLeave = () => {
if (onMouseEnter) {
setController((prev) => ({ ...prev, miniSidenav: true }));
setOnMouseEnter(false);
}
};
const handleConfiguratorOpen = () => {
setController((prev) => ({ ...prev, openConfigurator: !prev.openConfigurator }));
};
const filteredRoutes = routes.filter((route) => allowedPages.includes(route.key));
const configsButton = (
<MDBox
display="flex"
justifyContent="center"
alignItems="center"
width="3.25rem"
height="3.25rem"
sx={{ backgroundColor: "white" }}
shadow="sm"
borderRadius="50%"
position="fixed"
right="2rem"
bottom="2rem"
zIndex={99}
color="red"
onClick={handleConfiguratorOpen}
>
<Icon fontSize="small" color="inherit">
settings
</Icon>
</MDBox>
);
return (
<ThemeProvider theme={darkMode ? themeDark : theme}>
<CssBaseline />
{}
{configsButton}
<Routes>
<Route path="/authentication/sign-in" element={<SignIn />} />
{filteredRoutes.map((route) => (
<Route
key={route.key}
path={route.route}
element={
<PrivateRoute>
{route.component}
</PrivateRoute>
}
/>
))}
<Route path="*" element={<Navigate to="/callsummary" />} />
</Routes>
</ThemeProvider>
);
}
function App() {
return (
<MsalProvider instance={pca}>
<GlobalStateProvider>
<AppContent />
</GlobalStateProvider>
</MsalProvider>
);
}
export default App;
src/components/Auth/SignIn.js :
import React from 'react';
import { useMsal } from '@azure/msal-react';
import MDBox from '../UI/MDBox';
import { Button, TextField } from '@mui/material';
const SignIn = () => {
const { instance } = useMsal();
const handleLogout = () => {
instance.logout();
};
const handleSignIn = (event) => {
event.preventDefault();
instance.loginPopup().catch((error) => {
console.error("Login failed: ", error);
});
};
return (
<MDBox>
<form onSubmit={handleSignIn}>
<Button type="submit" variant="contained" color="primary">
Sign In
</Button>
<Button onClick={handleLogout} variant="outlined" color="secondary">
Logout
</Button>
</form>
</MDBox>
);
};
export default SignIn;
src/config/authConfig.js :
import { LogLevel } from "@azure/msal-browser";
export const msalConfig = {
auth: {
clientId: "<clientID>",
authority: "https://login.microsoftonline.com/<tenantID>",
redirectUri: "http://localhost:3000",
postLogoutRedirectUri: "http://localhost:3000",
},
cache: {
cacheLocation: "localStorage",
storeAuthStateInCookie: false,
},
system: {
loggerOptions: {
loggerCallback: (level, message, containsPii) => {
if (containsPii) return;
switch (level) {
case LogLevel.Error:
console.error(message);
return;
case LogLevel.Info:
console.info(message);
return;
case LogLevel.Verbose:
console.debug(message);
return;
case LogLevel.Warning:
console.warn(message);
return;
default:
return;
}
},
logLevel: LogLevel.Info,
piiLoggingEnabled: false,
},
},
};
export const loginRequest = {
scopes: ["User.Read"],
};
我在 Azure AD 中的身份验证 URI 下添加了以下 URL 作为单页应用程序。
http://localhost:3000
输出:
我成功登录并退出,如下所示。
http://localhost:3000/authentication/sign-in