Lune Logo

© 2025 Lune Inc.
All rights reserved.

support@lune.dev

Want to use over 200+ MCP servers inside your coding tools like Cursor?

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.

Fetching Image from SharePoint URL for PDF Generation Results in 401 Error Despite Azure App Permissions

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:

1. Fetching the image in the browser (works):

JAVASCRIPT
fetch('https://<sharepoint-site>/<image-url>') .then(response => response.blob()) .then(blob => { // Process the image });

2. Using Azure app authentication (fails with 401):

JAVASCRIPT
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));

3. Azure App Permissions:

  • 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.

Additional Context:

  • I am using Microsoft Graph API for authentication.
  • The SharePoint site and image are accessible when logged in via the browser.
  • The PDF generation library used is html2pdf.

Any guidance on resolving this authentication issue would be greatly appreciated!

2

Comments 0

Answers 3

Views 89

3 Answers

Answer by StackOverflowTychoAI Agent 1 month ago

0

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.


1. Check Your Token’s Audience

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://.sharepoint.com) rather than Graph.

Example:
If you’re using OAuth 2.0 to request a token, change the resource parameter (or scope) to your SharePoint domain. For instance:

HTTP
POST 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.


2. Review Delegated vs. App-Only Permissions

  • Delegated Permissions: These are used when acting on behalf of a signed-in user. If you’re using delegated permissions, the token is issued for the Microsoft Graph API in many cases, and the user’s context (including cookies) might be necessary.
  • App-Only Permissions: With app-only (client credentials flow), ensure that the image endpoint accepts app tokens. Some SharePoint endpoints (especially those serving static assets) may require delegated tokens or may not support app-only tokens.

Double-check that your authentication flow in your code (and in Azure AD) aligns with how the resource expects to see the token.


3. Validate API Permissions and Consent

Even though you’ve granted the app the Sites.Read.All and Files.Read.All permissions, verify that:

  • The permissions have been granted admin consent.
  • The token you retrieve contains these scopes.
  • You are not accidentally mixing delegated and app-only tokens (which might have different authorizations).

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.


4. Consider CORS and Fetch Environment

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.


5. Workaround with Proxying or Graph API

If you continue to face issues, you might consider:

  • Proxying the Request: Have your backend fetch the image using the correct token and then serve that image to the client. This way, your client does not need to worry about CORS or token errors.
  • Using Microsoft Graph to Retrieve the Image: If available, retrieve the image via a Microsoft Graph endpoint that supports the desired authentication method. You can then convert the image blob to a data URL (base64 string) to embed directly in your PDF.

Example of converting blob to a data URL:

JAVASCRIPT
fetch('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));

Summary

The common culprit is usually the token's audience or the type of token (delegated vs. app-only) that you're using. Make sure:

  • The token is issued for the SharePoint resource.
  • The permissions granted (and consented) match the requirements.
  • Consider alternative approaches like proxying the call or fetching the image through Microsoft Graph.

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

0

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

0

I have one image named logo.jpg stored in SharePoint site called sridemosite as below:

enter image description here

Initially, I created one app registration and added below API permissions of Application type with consent:

enter image description here

In my case, I used below code files to download image stored in SharePoint site locally and generate pdf with that image:

auth.js:

JAVASCRIPT
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:

JAVASCRIPT
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:

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:

enter image description here

To confirm that, I opened that pdf file which has SharePoint image as below:

enter image description here

Reference:

Download driveItem content - Microsoft Graph

No comments yet.

Discussion

No comments yet.