我正在尝试更新我在代码中通过图形 api 创建的现有用户,但我从 azure graph api 返回 403 错误

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

所以我试图向现有用户发送补丁请求以更新一些信息。我已经设置了 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"
        }
    }
}

解码后的令牌

{
  "aud": "https://graph.microsoft.com",
  "iss": "https://sts.windows.net/bf7e49f3-7209-4eba-af2c-5693ed7fd3b4/",
  "iat": 1735543772,
  "nbf": 1735543772,
  "exp": 1735547672,
  "aio": "k2BgYDj2P/ne5fNb9t2bIL2O6d5qJwA=",
  "app_displayname": "Nextjs",
  "appid": "5ea7d0e2-ccca-4703-a054-1c44ab151dbe",
  "appidacr": "1",
  "idp": "https://sts.windows.net/bf7e49f3-7209-4eba-af2c-5693ed7fd3b4/",
  "idtyp": "app",
  "oid": "6d2ad771-2803-48bf-96c5-5473c776bf7f",
  "rh": "1.ASMA80l-vwlyuk6vLFaT7X_TtAMAAAAAAAAAwAAAAAAAAAAGAQAjAA.",
  "roles": [
    "User.ReadBasic.All",
    "User.ReadWrite.All",
    "Application.ReadWrite.All",
    "Directory.ReadWrite.All",
    "User.Invite.All",
    "Directory.Read.All",
    "User.Read.All",
    "User.Export.All",
    "User.ManageIdentities.All"
  ],
  "sub": "6d2ad771-2803-48bf-96c5-5473c776bf7f",
  "tenant_region_scope": "EU",
  "tid": "bf7e49f3-7209-4eba-af2c-5693ed7fd3b4",
  "uti": "eECxwpxoNUWzB9XRTEQyAQ",
  "ver": "1.0",
  "wids": [
    "0997a1d0-0d1d-4acb-b408-d5ca73121e90"
  ],
  "xms_idrel": "7 32",
  "xms_tcdt": 1734346446,
  "xms_tdbr": "EU"
}

我是全局管理员,如您所见 role

这些也是我的 api 权限,因为我在 nextjs 代码中使用了 graph api。 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
azure next.js azure-active-directory microsoft-graph-api azure-ad-b2c
1个回答
0
投票

如果访问令牌不包含执行操作所需的范围和角色,通常会出现错误 “权限不足,无法完成操作”

我授予了API权限,如下所示:

enter image description here

对于 sample,我通过传递以下参数生成了访问令牌:

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

注意:对于仅应用程序访问,调用应用程序必须具有 User.ReadWrite.All 权限(对于最低权限)或 Directory.ReadWrite.All 权限(对于更高权限),以及 Microsoft Entra 中的用户管理员 角色。

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

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

enter image description here

enter image description here

重新生成访问令牌并且我能够成功更新用户详细信息

enter image description here

参考:

更新用户-Microsoft Graph v1.0 |微软

最新问题
© www.soinside.com 2019 - 2025. All rights reserved.