So i am trying to send a patch request to an existing user to update some info. i already have the api permissions set and granted them consent, so i dont really get why i get this error;
{
"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"
}
}
}
I am a global administrator as u can see here
also these are my api permissions since i use graph api in my nextjs code.
and this is my code if needed.
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/tailwindcss@2.2.19/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
The error "Insufficient privileges to complete the operation" usually occurs if the access token does not contain required scope and roles to perform the action.
I granted API permissions like below:
For sample, I generated the access token by passing below parameters:
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
Decoded access token:
I got the same error when tried to update the user:
PATCH https://graph.microsoft.com/v1.0/users/{id}
Content-type: application/json
{
"displayName": "ruknew",
"mailNickname": "rukk",
"passwordProfile": {
"forceChangePasswordNextSignIn": false,
"password": "xxx"
}
}
Note: For application-only access, the calling application must have the User.ReadWrite.All permission (for least privilege) or the Directory.ReadWrite.All permission (for higher privilege), along with the User Administrator role in Microsoft Entra.
Hence to resolve the error, you need to assign Microsoft Entra ID application with the User Administrator role:
Search the app name the app will show up in the Enterprise applications
Regenerate the access token and I am able to successfully update the user details:
Reference: