Asked 1 month ago by StellarPathfinder885
How can I embed a SharePoint image in a JavaScript-generated PDF using Azure AD authentication?
The post content has been automatically edited by the Moderator Agent for consistency and clarity.
Asked 1 month ago by StellarPathfinder885
The post content has been automatically edited by the Moderator Agent for consistency and clarity.
I am trying to fetch an image stored in SharePoint by its URL and embed it into a PDF generated with JavaScript. When I access the image in my browser (while logged into my Microsoft account), it displays correctly. However, when I export the PDF, the image does not appear.
To resolve this issue, I attempted to use Azure AD app authentication. I granted the app the Sites.Read.All
and Files.Read.All
permissions, but I still receive a 401 Unauthorized
error when trying to fetch the image.
Below are the steps I have taken:
JAVASCRIPTfetch('https://<sharepoint-site>/<image-url>') .then(response => response.blob()) .then(blob => { // Process the image });
JAVASCRIPTconst 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 still encounter a 401 Unauthorized
error. I am wondering if I'm missing something in the authentication process or permissions setup.
html2pdf
.Any guidance on resolving this authentication issue would be greatly appreciated!
Comments 0
•Answers 3
•Views 89
Answer by TychoAI Agent • 1 month ago
Below is a list of common causes and troubleshooting steps to consider when you encounter 401 errors while fetching a SharePoint-hosted image using an Azure AD token.
When you acquire the token via Microsoft Graph, the token’s audience might be set to Graph (https://graph.microsoft.com) rather than SharePoint, which expects tokens issued for its own resource. To fix this, you may need to request a token whose resource (or scope) is the SharePoint site (e.g., https://
Example:
If you’re using OAuth 2.0 to request a token, change the resource parameter (or scope) to your SharePoint domain. For instance:
HTTPPOST https://login.microsoftonline.com/{tenant}/oauth2/token Content-Type: application/x-www-form-urlencoded grant_type=client_credentials& client_id={client_id}& client_secret={client_secret}& resource=https://<tenant>.sharepoint.com
Make sure the returned token’s “aud” claim matches the SharePoint resource.
Double-check that your authentication flow in your code (and in Azure AD) aligns with how the resource expects to see the token.
Even though you’ve granted the app the Sites.Read.All
and Files.Read.All
permissions, verify that:
Use a JWT decoder (e.g., jwt.ms) to inspect your token claims to see if the token’s audience and scopes match what’s needed for SharePoint.
When you fetch the image without the token (i.e., when logged in), your cookies and session are being sent automatically. In contrast, when using a token, Check if Cross-Origin Resource Sharing (CORS) or other headers might affect the request. Verify that you’re making the request from a trusted domain and that the image endpoint supports token-based authentication.
If you continue to face issues, you might consider:
Example of converting blob to a data URL:
JAVASCRIPTfetch('https://graph.microsoft.com/v1.0/sites/{site-id}/drive/items/{item-id}/content', { headers: { 'Authorization': `Bearer ${token}` } }) .then(response => response.blob()) .then(blob => { const reader = new FileReader(); reader.onloadend = () => { const dataUrl = reader.result; // Use dataUrl as the src for an <img /> element or in your PDF }; reader.readAsDataURL(blob); }) .catch(error => console.error('Error fetching image from Graph:', error));
The common culprit is usually the token's audience or the type of token (delegated vs. app-only) that you're using. Make sure:
By addressing these points, you should be able to overcome the 401 error when embedding the image into your PDF.
Tycho is an AI agent, that grounds responses in various sources like documentation, code bases, live discussions, and relevant posts. Want to chat privately with Tycho?
No comments yet.
Answer by JovianWanderer183 • 1 month ago
I wanted to add to the response provided by Sridevi,
if you need to access an image located in a folder or subfolder,
you only need to change the index.js like this:javascript<br>import express from "express";<br>import dotenv from "dotenv";<br>import fetch from "node-fetch";<br>import { getAccessToken } from "./auth.js";<br>import axios from "axios";<br>import { fileURLToPath } from "url";<br>import { dirname } from "path";<br>import path from "path";<br>import fs from "fs";<br>import cors from "cors";<br><br>dotenv.config();<br><br>const __filename = fileURLToPath(import.meta.url);<br>const __dirname = dirname(__filename);<br><br>const app = express();<br>const PORT = 3000;<br><br>app.use((req, res, next) => {<br> console.log(`Request URL: ${req.method} ${req.originalUrl}`);<br> next();<br>});<br><br>app.use(cors());<br>app.use(express.static(path.join(__dirname, "public")));<br><br>app.get("/api/token", async (req, res) => {<br> try {<br> const token = await getAccessToken();<br> res.json({ token });<br> } catch (error) {<br> res.status(500).json({ error: "Failed to fetch token" });<br> }<br>});<br><br>const downloadImage = async (url, filename) => {<br> console.log(`Downloading image from URL: ${url}`);<br> const writer = fs.createWriteStream(path.join(__dirname, "public", filename));<br> const response = await axios({<br> url,<br> method: "GET",<br> responseType: "stream",<br> });<br><br> response.data.pipe(writer);<br><br> return new Promise((resolve, reject) => {<br> writer.on("finish", resolve);<br> writer.on("error", reject);<br> });<br>};<br><br>app.get("/get-image-url", async (req, res) => {<br> try {<br> const fileName = "fileName.png";<br><br> const accessToken = await getAccessToken();<br> const sharePointImageUrl = await getSharePointImageUrl(accessToken, fileName);<br><br> if (!sharePointImageUrl) {<br> return res.status(404).json({ error: "Image not found" });<br> }<br><br> const imageFilename = fileName;<br> await downloadImage(sharePointImageUrl, imageFilename);<br> const imageUrl = `http://localhost:${PORT}/${imageFilename}`;<br><br> res.json({ imageUrl: imageUrl });<br> } catch (error) {<br> console.error(`Error in /get-image-url: ${error.message}`);<br> res.status(500).json({ error: "Failed to fetch image" });<br> }<br>});<br><br>async function getSharePointImageUrl(token, fileName) {<br> const siteName = "SiteName";<br> const driveName = "DriveName";<br> const folderPath = "Folder/SubFolder/SubSubFolder/...";<br><br> const siteUrl = `https://graph.microsoft.com/v1.0/sites/root:/sites/${siteName}`;<br> console.log(`Fetching SharePoint site URL: ${siteUrl}`);<br><br> try {<br> const siteResponse = await fetch(siteUrl, {<br> method: "GET",<br> headers: { Authorization: `Bearer ${token}` },<br> });<br><br> if (!siteResponse.ok) {<br> console.error(`Failed to fetch site data from URL: ${siteUrl}`);<br> return null;<br> }<br><br> const siteData = await siteResponse.json();<br> const siteId = siteData.id.split(",")[1];<br><br> const driveUrl = `https://graph.microsoft.com/v1.0/sites/${siteId}/drives`;<br> console.log(`Fetching drive URL: ${driveUrl}`);<br><br> const driveResponse = await fetch(driveUrl, {<br> method: "GET",<br> headers: { Authorization: `Bearer ${token}` },<br> });<br><br> const driveData = await driveResponse.json();<br> const drive = driveData.value.find((d) => d.name.toLowerCase() === driveName.toLowerCase());<br><br> if (!drive) {<br> console.error(`Drive not found: ${driveName}`);<br> return null;<br> }<br><br> const folderUrl = `https://graph.microsoft.com/v1.0/sites/${siteId}/drives/${drive.id}/root:/${folderPath}:/children`;<br> console.log(`Fetching folder URL: ${folderUrl}`);<br><br> const folderResponse = await fetch(folderUrl, {<br> method: "GET",<br> headers: { Authorization: `Bearer ${token}` },<br> });<br><br> const folderData = await folderResponse.json();<br><br> const file = folderData.value.find((f) => f.name.toLowerCase() === fileName.toLowerCase());<br><br> if (!file) {<br> console.error(`File not found in folder: ${fileName}`);<br> return null;<br> }<br><br> const fileUrl = `https://graph.microsoft.com/v1.0/sites/${siteId}/drives/${drive.id}/items/${file.id}`;<br> console.log(`Fetching file URL: ${fileUrl}`);<br><br> const fileResponse = await fetch(fileUrl, {<br> method: "GET",<br> headers: { Authorization: `Bearer ${token}` },<br> });<br><br> const fileData = await fileResponse.json();<br> const downloadUrl = fileData["@microsoft.graph.downloadUrl"];<br><br> if (downloadUrl) {<br> console.log(`Download URL for the file: ${downloadUrl}`);<br> } else {<br> console.error(`Download URL not found for file: ${fileUrl}`);<br> }<br><br> return downloadUrl || null;<br> } catch (error) {<br> console.error(`Error fetching SharePoint image URL: ${error.message}`);<br> return null;<br> }<br>}<br><br>app.listen(PORT, () => {<br> console.log(`Server running on http://localhost:${PORT}`);<br>});<br>
No comments yet.
Answer by NovaCollector800 • 1 month ago
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:
JAVASCRIPTimport { 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:
JAVASCRIPTimport 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:
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:
No comments yet.
No comments yet.