javascriptphpazuremicrosoft-graph-apisharepoint-online

Display Image stored in a sharepoint Site, on a pdf that i want to export


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

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

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

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

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:

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?

Additional Context:

Any help or guidance on resolving this issue would be greatly appreciated!


Solution

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

    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:

    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