所以我试图向现有用户发送补丁请求以更新一些信息。我已经设置了 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"
}
这些也是我的 api 权限,因为我在 nextjs 代码中使用了 graph 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
如果访问令牌不包含执行操作所需的范围和角色,通常会出现错误 “权限不足,无法完成操作”。
我授予了API权限,如下所示:
对于 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
解码后的访问令牌:
我在尝试更新用户时遇到了同样的错误:
PATCH https://graph.microsoft.com/v1.0/users/{id}
Content-type: application/json
{
"displayName": "ruknew",
"mailNickname": "rukk",
"passwordProfile": {
"forceChangePasswordNextSignIn": false,
"password": "xxx"
}
}
注意:对于仅应用程序访问,调用应用程序必须具有 User.ReadWrite.All 权限(对于最低权限)或 Directory.ReadWrite.All 权限(对于更高权限),以及 Microsoft Entra 中的用户管理员 角色。
因此要解决错误,您需要为 Microsoft Entra ID 应用程序分配用户管理员角色:
搜索应用程序名称,该应用程序将显示在企业应用程序中
重新生成访问令牌并且我能够成功更新用户详细信息:
参考: