You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React+MSAL+Azure SSO登录跳转异常,请求技术支持

React + MSAL + Azure SSO 跳转异常问题排查与解决

问题现象

点击SSO登录按钮后,短暂跳转到/home页面随即返回/signin,网络面板无token生成。Azure端已完成以下配置:

  • redirectUri设置为http://localhost:5173/home
  • 添加openid、email、profile权限范围
  • 启用access token和id token

相关代码(修正后)

Authconfig.js

export const msalConfig = {
  auth: {
    clientId: "client-id",
    authority: "https://login.microsoftonline.com/tenant-id",
    redirectUri: "http://localhost:5173/home",
  },
  cache: {
    cacheLocation: "sessionStorage",  
    storeAuthStateInCookie: false, // 修复原代码拼写错误:issuesstoreAuthStateInCookie
  },
};

// 补充缺失的loginRequest配置
export const loginRequest = {
  scopes: ["openid", "email", "profile"]
};

main.jsx

import React from 'react';
import ReactDOM from 'react-dom/client';
import { MsalProvider } from '@azure/msal-react';
import { PublicClientApplication } from '@azure/msal-browser';
import { msalConfig } from './Authconfig';  
import { RouterProvider, createBrowserRouter } from 'react-router-dom';
import { ProtectedRoutes } from './utils/ProtectedRoutes.jsx';
import { AboutComponent } from './AboutComponent.jsx';
import { AdminComponent } from './AdminComponent.jsx';
import { BuildComponent } from './BuildComponent.jsx';
import { ContactusComponent } from './ContactusComponent.jsx';
import { FeedbackComponent } from './FeedbackComponent.jsx';
import { LandingPageComponent } from './LandingPageComponent.jsx';
import { LoginPageComponent } from './LoginPageComponent.jsx';
import { ReleasenotesComponent } from './ReleasenotesComponent.jsx';
import { UserHomeComponent } from './UserHomeComponent.jsx';
import { PricingComponent } from './PricingComponent.jsx';
import { UserProfileComponent } from './UserProfileComponent.jsx';
import './index.css';
import { AuthProvider } from './utils/AuthContext';

const msalInstance = new PublicClientApplication(msalConfig);

const router = createBrowserRouter([
  {
    path: '/',
    element: <LandingPageComponent />,
    errorElement: <div>404 not found</div>,
    children: [
      { path: '', element: <AboutComponent /> },
      { path: 'pricing', element: <PricingComponent /> },
      { path: 'releasenotes', element: <ReleasenotesComponent /> },
      { path: 'contactus', element: <ContactusComponent /> },
      { path: 'signin', element: <LoginPageComponent /> },
      { 
        element: <ProtectedRoutes />,
        children: [
          { path: 'home', element: <UserHomeComponent /> },
          { path: 'build', element: <BuildComponent /> },
          { path: 'admin', element: <AdminComponent /> },
          { path: 'userprofile', element: <UserProfileComponent /> },
          { path: 'feedback', element: <FeedbackComponent /> },
        ],
      },
    ],
  },
]);

ReactDOM.createRoot(document.getElementById('root')).render(
  <React.StrictMode>
    <MsalProvider instance={msalInstance}>
      <AuthProvider>
        <RouterProvider router={router} />
      </AuthProvider>
    </MsalProvider>
  </React.StrictMode>
);

LoginPageComponent.jsx

import React, { useContext, useEffect } from 'react';
import { useMsal } from '@azure/msal-react';
import { loginRequest } from './Authconfig';
import { useNavigate } from 'react-router-dom';
import loginimage from './assets/loginimage.svg';
import { AuthContext } from './utils/AuthContext';

export const LoginPageComponent = () => {
  const { instance } = useMsal();
  const navigate = useNavigate();
  const { login } = useContext(AuthContext);

  const handleLogin = () => {
    instance.loginRedirect(loginRequest);
  };

  useEffect(() => {
    instance.handleRedirectPromise()
      .then((response) => {
        if (response) {
          console.log('Authentication Response:', response);
          login(response.account);
          // 移除手动导航:redirectUri已指向/home,无需重复跳转
        }
      })
      .catch((error) => {
        console.error('Authentication Error:', error);
      });
  }, [instance, navigate, login]);

  return (
    <div className="bg-white">
      <div className="grid grid-cols-2 justify-center items-center m-20">
        <div>
          <img
            loading="lazy"
            src={loginimage}
            className="self-end max-w-full aspect-square w-[540px]"
          />
        </div>
        <div className="flex flex-col ml-5">
          <div className="flex flex-col p-10 mt-5 w-fit items-center justify-center rounded-lg shadow-sm bg-sky-600 bg-opacity-20 text-zinc-800 max-md:px-5 max-md:mt-10">
            <div className="text-4xl font-black text-sky-600">
              <span className="font-medium">Welcome to</span>
              <br />
              <span className="text-5xl text-sky-600 leading-[62px]">
                buildNow Portal
              </span>
            </div>
            <button onClick={handleLogin} className="w-96 px-8 py-3 ml-6 rounded-lg bg-indigo-500 text-gray-100 border border-gray-200 placeholder-gray-500 text-sm tracking-wide font-semibold hover:bg-indigo-700 transition-all duration-300 ease-in-out flex items-center justify-center focus:shadow-outline focus:outline-none mt-5">
              <svg className="w-6 h-6 -ml-2" fill="none" stroke="currentColor">
                <path d="M16 21v-2a4 4 0 00-4-4H5a4 4 0 00-4 4v2" />
                <circle cx="8.5" cy="7" r="4" />
                <path d="M20 8v6M23 11h-6" />
              </svg>
              <span className="ml-3">
                LOGIN
              </span>
            </button>
          </div>
        </div>
      </div>
    </div>
  );
};

AuthContext.jsx

import React, { createContext, useState, useEffect } from 'react';
import { useMsal } from '@azure/msal-react';

const AuthContext = createContext();

const AuthProvider = ({ children }) => {
  const { instance } = useMsal();
  const [user, setUser] = useState(() => {
    const storedUser = localStorage.getItem('user');
    return storedUser ? JSON.parse(storedUser) : null;
  });

  // 同步MSAL账户状态到AuthContext
  useEffect(() => {
    const accounts = instance.getAllAccounts();
    if (accounts.length > 0 && !user) {
      setUser(accounts[0]);
      localStorage.setItem('user', JSON.stringify(accounts[0]));
    }
  }, [instance, user]);

  const login = (account) => {
    setUser(account);
    localStorage.setItem('user', JSON.stringify(account));
  };

  const logout = () => {
    instance.logoutRedirect();
    setUser(null);
    localStorage.removeItem('user');
  };

  return (
    <AuthContext.Provider value={{ user, setUser, login, logout }}>
      {children}
    </AuthContext.Provider>
  );
};

export { AuthContext, AuthProvider };

核心解决步骤

  1. 修复缓存配置拼写错误:原代码中issuesstoreAuthStateInCookie是拼写错误,改为storeAuthStateInCookie,否则MSAL无法正确管理认证缓存。
  2. 补充loginRequest配置:必须明确请求的权限范围,否则MSAL不会生成token。
  3. 移除重复导航逻辑:redirectUri已设置为/home,Azure会自动跳转,手动调用navigate会导致路由状态冲突。
  4. 同步MSAL与AuthContext状态:添加监听MSAL账户变化的逻辑,确保ProtectedRoutes能正确识别登录状态。
  5. 检查ProtectedRoutes实现:确保路由守卫正确读取AuthContext中的user状态,示例实现:
import React, { useContext } from 'react';
import { Navigate, Outlet } from 'react-router-dom';
import { AuthContext } from './AuthContext';

export const ProtectedRoutes = () => {
  const { user } = useContext(AuthContext);
  return user ? <Outlet /> : <Navigate to="/signin" />;
};
  1. 验证Azure配置:确认redirectUri完全匹配(协议、端口、路径无偏差),权限已授予管理员同意(租户应用),且SPA应用使用授权码流程+PKCE。

内容的提问来源于stack exchange,提问作者Airin Anil

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 11:19:50