I am trying to fetch an image stored in SharePoint by its URL and embed it into a PDF that I generate using JavaScript. When I fetch the image in my browser (while logged into my Microsoft account), it works fine. However, when I attempt to export the PDF, the image does not display.
To resolve this, I tried using an Azure app for authentication. I granted the app Sites.Read.All
and Files.Read.All
permissions, but I am still getting a 401 Unauthorized
error when trying to fetch the image.
Here’s what I’ve tried so far:
fetch('https://<sharepoint-site>/<image-url>')
.then(response => response.blob())
.then(blob => {
// Process the image
});
const token = await getAccessToken(); // Function to get token using Azure AD
fetch('https://<sharepoint-site>/<image-url>', {
headers: {
'Authorization': `Bearer ${token}`
}
})
.then(response => {
if (!response.ok) throw new Error('Network response was not ok');
return response.blob();
})
.then(blob => {
// Process the image
})
.catch(error => console.error('Error fetching image:', error));
Sites.Read.All
Files.Read.All
Screenshot of API permissions for the Azure APP
Despite these permissions, I am still encountering a 401 Unauthorized
error. Am I missing something in the authentication process or the permissions setup?
html2pdf
.Any help or guidance on resolving this issue would be greatly appreciated!
I have one image named logo.jpg
stored in SharePoint site called sridemosite as below:
Initially, I created one app registration and added below API permissions of Application type with consent:
In my case, I used below code files to download image stored in SharePoint site locally and generate pdf with that image:
auth.js:
import { ConfidentialClientApplication } from "@azure/msal-node";
import dotenv from "dotenv";
dotenv.config();
const msalConfig = {
auth: {
clientId: process.env.CLIENT_ID,
authority: process.env.AUTHORITY,
clientSecret: process.env.CLIENT_SECRET,
},
};
const tokenRequest = {
scopes: [process.env.GRAPH_SCOPE], // https://graph.microsoft.com/.default
};
const cca = new ConfidentialClientApplication(msalConfig);
export async function getAccessToken() {
try {
const response = await cca.acquireTokenByClientCredential(tokenRequest);
return response.accessToken;
} catch (error) {
console.error("Error acquiring token:", error);
throw error;
}
}
index.js:
import express from "express";
import dotenv from "dotenv";
import fetch from "node-fetch";
import { getAccessToken } from "./auth.js";
import axios from "axios";
import { fileURLToPath } from "url";
import { dirname } from "path";
import path from "path";
import fs from "fs";
import cors from "cors";
dotenv.config();
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const app = express();
const PORT = 3000;
app.use(cors());
app.use(express.static(path.join(__dirname, "public")));
app.get("/api/token", async (req, res) => {
try {
const token = await getAccessToken();
res.json({ token });
} catch (error) {
res.status(500).json({ error: "Failed to fetch token" });
}
});
const downloadImage = async (url, filename) => {
const writer = fs.createWriteStream(path.join(__dirname, "public", filename));
const response = await axios({
url,
method: "GET",
responseType: "stream",
});
response.data.pipe(writer);
return new Promise((resolve, reject) => {
writer.on("finish", resolve);
writer.on("error", reject);
});
};
app.get("/get-image-url", async (req, res) => {
try {
const accessToken = await getAccessToken();
const sharePointImageUrl = await getSharePointImageUrl(accessToken);
if (!sharePointImageUrl) {
return res.status(404).json({ error: "Image not found" });
}
const imageFilename = "logo.jpg";
await downloadImage(sharePointImageUrl, imageFilename);
const imageUrl = `http://localhost:${PORT}/${imageFilename}`;
res.json({ imageUrl: imageUrl });
} catch (error) {
res.status(500).json({ error: "Failed to generate PDF" });
}
});
async function getSharePointImageUrl(token) {
const siteName = "sridemosite";
const driveName = "sridoclib";
const filePath = "logo.jpg";
const siteUrl = `https://graph.microsoft.com/v1.0/sites/root:/sites/${siteName}`;
try {
const siteResponse = await fetch(siteUrl, {
method: "GET",
headers: { Authorization: `Bearer ${token}` },
});
if (!siteResponse.ok) {
return null;
}
const siteData = await siteResponse.json();
const siteId = siteData.id.split(",")[1];
const driveUrl = `https://graph.microsoft.com/v1.0/sites/${siteId}/drives`;
const driveResponse = await fetch(driveUrl, {
method: "GET",
headers: { Authorization: `Bearer ${token}` },
});
const driveData = await driveResponse.json();
const drive = driveData.value.find((d) => d.name.toLowerCase() === driveName.toLowerCase());
if (!drive) {
return null;
}
const fileUrl = `https://graph.microsoft.com/v1.0/sites/${siteId}/drives/${drive.id}/root/children/${filePath}`;
const fileResponse = await fetch(fileUrl, {
method: "GET",
headers: { Authorization: `Bearer ${token}` },
});
const fileData = await fileResponse.json();
const downloadUrl = fileData["@microsoft.graph.downloadUrl"];
return downloadUrl || null;
} catch (error) {
return null;
}
}
app.listen(PORT, () => {
console.log(`Server running on http://localhost:${PORT}`);
});
public/index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Generate PDF with Image</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/html2pdf.js/0.9.2/html2pdf.bundle.js"></script>
<style>
body {
font-family: Arial, sans-serif;
text-align: center;
padding: 20px;
}
#content {
text-align: center;
margin-top: 30px;
}
img {
max-width: 100%;
height: auto;
margin: 20px 0;
}
#content h1 {
font-size: 24px;
}
#content p {
font-size: 16px;
color: #555;
}
#generate-pdf {
margin-top: 20px;
padding: 10px 20px;
font-size: 16px;
cursor: pointer;
background-color: #4CAF50;
color: white;
border: none;
border-radius: 5px;
transition: background-color 0.3s;
}
#generate-pdf:disabled {
background-color: #ccc;
cursor: not-allowed;
}
#generate-pdf:hover {
background-color: #45a049;
}
</style>
</head>
<body>
<h1>Generate PDF with SharePoint Image</h1>
<div id="content">
<h1>SharePoint Image in PDF</h1>
<img id="image" alt="SharePoint logo" />
<p>This content is added along with the SharePoint image in the PDF.</p>
</div>
<button id="generate-pdf" disabled>Generate PDF</button>
<script>
fetch('/get-image-url')
.then(response => response.json())
.then(data => {
if (data.imageUrl) {
const imageElement = document.getElementById('image');
imageElement.src = data.imageUrl;
imageElement.onload = () => {
console.log("Image loaded successfully");
document.getElementById('generate-pdf').disabled = false;
};
imageElement.onerror = () => {
console.error("Error loading image");
alert("Failed to load image. Please try again.");
};
} else {
console.error('Failed to fetch image URL');
}
})
.catch(error => console.error('Error:', error));
document.getElementById('generate-pdf').addEventListener('click', function() {
const imageElement = document.getElementById('image');
if (imageElement.complete) {
const element = document.getElementById('content');
const opt = {
margin: 10,
filename: 'sharepoint-image.pdf',
image: { type: 'jpeg', quality: 0.98 },
html2canvas: { dpi: 192, letterRendering: true },
jsPDF: { unit: 'mm', format: 'a4', orientation: 'portrait' }
};
html2pdf()
.from(element)
.set(opt)
.save()
.then(() => {
console.log("PDF generated with SharePoint image");
console.log("PDF generated successfully");
});
} else {
alert("Please wait for the image to load before generating the PDF.");
}
});
</script>
</body>
</html>
When I ran node index.js
and visited http://localhost:3000/ in browser, it has Generate PDF button that creates pdf with SharePoint image:
To confirm that, I opened that pdf file which has SharePoint image as below:
Reference: