Microsoft 身份验证错误:使用 loginPopup 时反应“hash_empty_error”

问题描述 投票:0回答:1

我无法理解在尝试使用 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;
reactjs azure-ad-msal msal
1个回答
0
投票

我在以下 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

enter image description here

输出:

我成功登录并退出,如下所示。

http://localhost:3000/authentication/sign-in

enter image description here

enter image description here

enter image description here

© www.soinside.com 2019 - 2024. All rights reserved.