代码之家  ›  专栏  ›  技术社区  ›  Adnane Bady Soussi

我试图更新我在代码中通过graphapi创建的一个现有用户,但我从azure graphapi返回了403错误

  •  0
  • Adnane Bady Soussi  · 技术社区  · 7 月前

    因此,我试图向现有用户发送补丁请求以更新一些信息。我已经设置了api权限并授予了他们同意,所以我真的不明白为什么会出现这个错误;

    {
        "error": {
            "code": "Authorization_RequestDenied",
            "message": "Insufficient privileges to complete the operation.",
            "innerError": {
                "date": "2024-12-30T06:59:17",
                "request-id": "55f9f873-b7b3-424a-95ab-5b5f21e3593b",
                "client-request-id": "// wont show this"
            }
        }
    }
    

    如您所见,我是一名全球管理员 role

    这些也是我的api权限,因为我在nextjs代码中使用了graphapi。 api

    如果需要,这是我的代码。

    import { useState, useEffect } from "react";
    import axios from "axios";
    import Head from "next/head";
    import { useRouter } from "next/router";
    
    interface FormData {
      displayName: string;
      mailNickname: string;
      password: string;
      customField: string;
      organizationalUnitCode: string;
    }
    
    interface OrganizationalUnit {
      code: string;
      name: string;
    }
    
    const getAccessToken = async (): Promise<string> => {
      try {
        const response = await axios.post("/api/register");
        return response.data.accessToken;
      } catch (error) {
        console.error("Error fetching token:", error);
        throw new Error("Failed to fetch access token");
      }
    };
    const findExistingUserByEmail = async (accessToken: string, email: string) => {
      try {
        const response = await axios.get(
          `https://graph.microsoft.com/v1.0/users?$filter=mail eq '${email}'`,
          {
            headers: {
              Authorization: `Bearer ${accessToken}`,
              "Content-Type": "application/json",
            },
          }
        );
    
        if (response.data.value.length > 0) {
          const user = response.data.value[0];
          console.log("Found user:", user);  // Log the user to check the id
          return user; // This returns the user object with id and other properties
        }
    
        return null;
      } catch (error) {
        const err = error as any;
        console.error("Error finding user by email:", err.response?.data || err.message);
        throw new Error("Failed to find user by email");
      }
    };
    
    
    
    const updateUser = async (accessToken: string, userId: string, userData: FormData) => {
      console.log(userId)
      try {
        const response = await axios.patch(
          `https://graph.microsoft.com/v1.0/users/${userId}`,
          {
            id: userId,
            displayName: userData.displayName,
            mailNickname: userData.mailNickname,
            passwordProfile: userData.password ? { password: userData.password } : undefined,
            [ `extension_${process.env.NEXT_PUBLIC_AZURE_AD_B2C_Extension_ID}_organizationalUnitCode` ]: userData.organizationalUnitCode,
          },
          {
            headers: {
              Authorization: `Bearer ${accessToken}`,
              "Content-Type": "application/json",
            },
          }
        );
    
        console.log("User updated successfully:", response.data);
        return response.data;
      } catch (error) {
      const err = error as any;
      console.error("Error updating user:", err.response?.data || err.message);
      throw new Error(`Failed to update user: ${err.response?.data?.error?.message || err.message}`);
    }
    
    };
    
    
    const createUser = async (accessToken: string, userData: FormData) => {
      try {
        const mailNickname = userData.mailNickname;
        const email = userData.displayName;
    
        if (!email.includes("@")) {
          throw new Error("Display Name must be a valid email address.");
        }
    
        const domain = email.split("@")[1];
        const formattedMailNickname = `${mailNickname}_${domain.split(".")[0]}.com`;
        const formattedEmail = `${formattedMailNickname}#EXT#@floadingwatkanikladen.onmicrosoft.com`;
    
        const response = await axios.post(
          "https://graph.microsoft.com/v1.0/users",
          {
            accountEnabled: true,
            displayName: userData.displayName,
            mailNickname: formattedMailNickname,
            userPrincipalName: formattedEmail,
            otherMails: [userData.displayName],
            passwordProfile: {
              forceChangePasswordNextSignIn: false,
              password: userData.password,
            },
            [`extension_${process.env.NEXT_PUBLIC_AZURE_AD_B2C_Extension_ID}_organizationalUnitCode`]:
              userData.organizationalUnitCode,
          },
          {
            headers: {
              Authorization: `Bearer ${accessToken}`,
              "Content-Type": "application/json",
            },
          }
        );
    
        return response.data;
      } catch (err) {
        console.error("Error creating user:", err);
        throw err;
      }
    };
    
    const SignupForm = () => {
      const router = useRouter();
      const { email, organizationalUnitCode } = router.query;
    
      const [formData, setFormData] = useState<FormData>({
        displayName: "",
        mailNickname: "",
        password: "",
        customField: "",
        organizationalUnitCode: "",
      });
    
      const [loading, setLoading] = useState<boolean>(false);
      const [error, setError] = useState<string | null>(null);
      const [success, setSuccess] = useState<string | null>(null);
      const [organizationalUnits, setOrganizationalUnits] = useState<OrganizationalUnit[]>([]);
    
      useEffect(() => {
        const fetchOrganizationalUnits = async () => {
          try {
            const response = await axios.get(
              "https://watkanikladenapi2-gwhpc3htfzhrh7e2.westeurope-01.azurewebsites.net/organizational-units"
            );
            setOrganizationalUnits(response.data.organizationalUnits);
          } catch (error) {
            console.error("Error fetching organizational units:", error);
          }
        };
    
        fetchOrganizationalUnits();
      }, []);
    
      useEffect(() => {
        if (email) {
          setFormData((prevData) => ({
            ...prevData,
            mailNickname: email as string,
            organizationalUnitCode: organizationalUnitCode as string || "",
          }));
        }
      }, [email, organizationalUnitCode]);
    
      const handleCreate = async (e: React.FormEvent) => {
        e.preventDefault();
        setLoading(true);
        setError(null);
        setSuccess(null);
    
        try {
          const accessToken = await getAccessToken();
          const result = await createUser(accessToken, formData);
          console.log("User created:", result);
          setSuccess("User created successfully!");
        } catch (err) {
          console.error("Error creating user:", err);
          setError(`An error occurred: ${(err as Error).message}`);
        } finally {
          setLoading(false);
        }
      };
    
      const handleUpdate = async (e: React.FormEvent) => {
        e.preventDefault();
        setLoading(true);
        setError(null);
        setSuccess(null);
      
        try {
          const accessToken = await getAccessToken();
          
          // Find the existing user by email
          const existingUser = await findExistingUserByEmail(accessToken, formData.displayName);
      
          if (existingUser) {
            console.log("User ID to update:", existingUser.id); // Check the ID here
            const result = await updateUser(accessToken, existingUser.id, formData);
            console.log("User updated:", result);
            setSuccess("User updated successfully!");
          } else {
            setError("User not found for updating.");
          }
        } catch (err) {
          console.error("Error updating user:", err);
          setError(`An error occurred: ${(err as Error).message}`);
        } finally {
          setLoading(false);
        }
      };
      
    
      return (
        <>
          <Head>
            <link
              href="https://cdn.jsdelivr.net/npm/[email protected]/dist/tailwind.min.css"
              rel="stylesheet"
            />
          </Head>
    
          <div className="min-h-screen flex items-center justify-center bg-gray-100 py-12 px-4">
            <div className="bg-white p-8 rounded-lg shadow-lg max-w-md w-full">
              <h1 className="text-2xl font-bold text-center text-gray-800 mb-6">Sign Up / Update</h1>
              <form>
                <input
                  type="text"
                  placeholder="Display Name"
                  value={formData.displayName}
                  onChange={(e) => setFormData({ ...formData, displayName: e.target.value })}
                  className="w-full p-3 mb-4 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
                />
                <input
                  type="text"
                  placeholder="Mail Nickname"
                  value={formData.mailNickname}
                  onChange={(e) => setFormData({ ...formData, mailNickname: e.target.value })}
                  className="w-full p-3 mb-4 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
                  readOnly={!!email}
                />
                <input
                  type="password"
                  placeholder="Password"
                  value={formData.password}
                  onChange={(e) => setFormData({ ...formData, password: e.target.value })}
                  className="w-full p-3 mb-4 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
                />
    
                <select
                  value={formData.organizationalUnitCode}
                  onChange={(e) => setFormData({ ...formData, organizationalUnitCode: e.target.value })}
                  className="w-full p-3 mb-4 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
                >
                  <option value="">Select Organizational Unit</option>
                  {organizationalUnits.map((unit) => (
                    <option key={unit.code} value={unit.code}>
                      {unit.name}
                    </option>
                  ))}
                </select>
    
                <button
                  onClick={handleCreate}
                  disabled={loading}
                  className="w-full bg-green-500 hover:bg-green-600 text-white font-bold py-3 rounded-lg focus:outline-none focus:ring-2 focus:ring-green-500 mb-4"
                >
                  {loading ? "Processing..." : "Create User"}
                </button>
                <button
                  onClick={handleUpdate}
                  disabled={loading}
                  className="w-full bg-blue-500 hover:bg-blue-600 text-white font-bold py-3 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
                >
                  {loading ? "Processing..." : "Update User"}
                </button>
    
                {error && <p className="text-red-500 text-center mt-4">{error}</p>}
                {success && <p className="text-green-500 text-center mt-4">{success}</p>}
              </form>
            </div>
          </div>
        </>
      );
    };
    
    export default SignupForm;
    
    enter code here
    
    1 回复  |  直到 7 月前
        1
  •  1
  •   Rukmini    7 月前

    错误 “权限不足,无法完成操作” 如果访问令牌不包含执行操作所需的作用域和角色,则通常会发生这种情况。

    我同意了 API权限 如下所示:

    enter image description here

    For 样品 ,我通过传递以下参数生成了访问令牌:

    https://login.microsoftonline.com/B2CTenantID/oauth2/v2.0/token
    
    grant_type: client_credentials
    client_id: ClientID
    client_secret: Secret
    scope: https://graph.microsoft.com/.default
    

    enter image description here

    解码的访问令牌:

    enter image description here

    在尝试更新用户时,我也遇到了同样的错误:

    PATCH https://graph.microsoft.com/v1.0/users/{id}
    Content-type: application/json
    
    {
      "displayName": "ruknew",
      "mailNickname": "rukk",
      "passwordProfile": {
        "forceChangePasswordNextSignIn": false,
        "password": "xxx"
      }
    }
    
    

    enter image description here

    注: :对于仅限应用程序的访问,调用应用程序必须具有 用户。读写。全部 权限(最低特权)或 目录。读写。全部 权限(用于更高权限),以及 用户管理员 微软Entra的角色。

    因此 解决错误 ,您需要为Microsoft Entra ID应用程序分配 用户管理员 角色:

    搜索应用程序名称,该应用程序将显示在企业应用程序中

    enter image description here

    enter image description here

    重新生成访问令牌,然后 我能够成功更新用户详细信息 :

    enter image description here

    参考:

    Update user - Microsoft Graph v1.0 | Microsoft