Hi Ron,
Based on my research, I have compiled the answer. Be aware that I have not implemented a similar solution to this before, but this approach is a good suggestion for a future improvement. You can also use ChatGPT to support you on this solution.
1 - Correct, not NPM functionality at the moment, unless you create a custom action in React.
2 - I believe Yes, OneDrive can be used with this approach.
The file is uploaded to OneDrive using Microsoft Graph, then Graph creates a share/download link. The current code in the document follows that pattern.
3 - Yes, this is OAuth validation/authentication.
More specifically, it uses the OAuth 2.0 client credentials flow, where the app gets an access token using client_id, client_secret, and tenant_id, then uses that token to call Microsoft Graph. OAuth 2.0 client credentials flow on the Microsoft identity platform - Microsoft identity platform | Microsoft Learn
4 - In theory, yes, any cloud service with OAuth + file upload API could be used.
But for proof of concept, OneDrive via Microsoft Graph is the best option because Graph handles the Microsoft authentication model and provides simple endpoints for upload and link creation.
This is the workflow:
1. Get OAuth token
POST Sign in to your account
Body:
client_id=…
client_secret=…
scope=https://graph.microsoft.com/.default
grant_type=client_credentials
Then use the returned token:
Authorization: Bearer ACCESS_TOKEN
Upload file:
PUT https://graph.microsoft.com/v1.0/users/{USER_UPN}/drive/root:/report.pdf:/content
Then create link:
POST https://graph.microsoft.com/v1.0/users/{USER_UPN}/drive/items/{FILE_ID}/createLink
Body:
{
"type": "view",
"scope": "anonymous"
}
If anonymous sharing is blocked by the Microsoft tenant, use:
{
"type": "view",
"scope": "organization"
}
In Five, you would normally store these as credentials/variables:
TENANT_ID
CLIENT_ID
CLIENT_SECRET
DEFAULT_USER_UPN
Then your Five action/process can return:
result.Results = JSON.stringify({
ok: true,
fileName: fileName,
downloadUrl: downloadUrl
});
So the user can click the returned downloadUrl.
The main implementation point is: Five does not need NPM packages. It only needs five.httpClient() to call the OAuth token endpoint and Microsoft Graph endpoints.
You do not need to save it to a local/server file first. The decoded PDF bytes can be sent directly in the HTTP request body to Microsoft Graph.
Graph’s upload endpoint expects the request body to be the binary stream of the file, not a file path. So the flow should be:
base64 PDF string → decode to binary bytes → HTTP PUT body → Graph/OneDrive
The important part is that we must send the decoded binary data, not the original base64 text. The request should also use a PDF filename such as report.pdf and content type application/pdf.
For small files, Microsoft Graph supports uploading the file content directly with PUT /drive/root:/filename:/content, up to 250 MB. For larger files, we would need an upload session/chunked upload Upload small files - Microsoft Graph v1.0 | Microsoft Learn
This is a sample code for a starting point. You can refer to the AI to help you understand it:
function UploadBase64PDFToOneDrive(five, context, result, values) {
five.log("UploadBase64PDFToOneDrive START");
const EXPIRY_BUFFER_MS = 2 * 60 * 1000;
const TENANT_ID = values.credentials.TENANT_ID;
const CLIENT_ID = values.credentials.CLIENT_ID;
const CLIENT_SECRET = values.credentials.CLIENT_SECRET;
const USER_UPN = values.credentials.DEFAULT_USER_UPN;
if (!TENANT_ID || !CLIENT_ID || !CLIENT_SECRET || !USER_UPN) {
throw new Error("Missing Microsoft Graph credentials.");
}
if (!values.pdfBase64) {
throw new Error("Missing pdfBase64.");
}
// Remove data URL prefix if present:
// data:application/pdf;base64,JVBERi0x...
let pdfBase64 = values.pdfBase64;
if (pdfBase64.indexOf(",") >= 0) {
pdfBase64 = pdfBase64.split(",")[1];
}
pdfBase64 = pdfBase64.replace(/\s/g, "");
// -----------------------------
// 1) Get cached or new token
// -----------------------------
function getValidAccessToken() {
let tokenCacheRaw = five.getVariable("graphTokenCache");
let tokenCache = tokenCacheRaw
? JSON.parse(tokenCacheRaw)
: {
accessToken: null,
expiresAtMs: 0
};
const now = Date.now();
if (
tokenCache.accessToken &&
now < tokenCache.expiresAtMs - EXPIRY_BUFFER_MS
) {
return tokenCache.accessToken;
}
const tokenClient = five.httpClient();
const tokenUrl =
"https://login.microsoftonline.com/" +
encodeURIComponent(TENANT_ID) +
"/oauth2/v2.0/token";
function encodeForm(data) {
let pairs = [];
for (let key in data) {
pairs.push(
encodeURIComponent(key) + "=" + encodeURIComponent(data[key])
);
}
return pairs.join("&");
}
tokenClient.setContentType("application/x-www-form-urlencoded");
tokenClient.setContent(
encodeForm({
client_id: CLIENT_ID,
client_secret: CLIENT_SECRET,
scope: "https://graph.microsoft.com/.default",
grant_type: "client_credentials"
})
);
const tokenRes = tokenClient.post(tokenUrl);
if (!tokenRes || tokenRes.isOk() === false) {
throw new Error("Token request failed: " + JSON.stringify(tokenRes));
}
let tokenData = tokenRes.response;
if (typeof tokenData === "string") {
tokenData = JSON.parse(tokenData);
}
if (!tokenData.access_token) {
throw new Error("No access token returned: " + JSON.stringify(tokenData));
}
tokenCache = {
accessToken: tokenData.access_token,
expiresAtMs: Date.now() + tokenData.expires_in * 1000
};
five.setVariable("graphTokenCache", JSON.stringify(tokenCache));
return tokenData.access_token;
}
const accessToken = getValidAccessToken();
// -----------------------------
// 2) Decode base64 PDF to binary
// -----------------------------
function base64ToBinary(base64) {
if (typeof atob === "function") {
return atob(base64);
}
throw new Error(
"Base64 decoding failed. This Five runtime does not support atob()."
);
}
const pdfBinary = base64ToBinary(pdfBase64);
// Quick PDF validation
if (pdfBinary.substring(0, 5) !== "%PDF-") {
throw new Error(
"Decoded file does not look like a PDF. First bytes: " +
pdfBinary.substring(0, 10)
);
}
// -----------------------------
// 3) Upload binary PDF to OneDrive
// -----------------------------
const fileName = "report-" + Date.now() + ".pdf";
const uploadUrl =
"https://graph.microsoft.com/v1.0/users/" +
encodeURIComponent(USER_UPN) +
"/drive/root:/" +
encodeURIComponent(fileName) +
":/content";
const uploadClient = five.httpClient();
uploadClient.addHeader("Authorization", "Bearer " + accessToken);
uploadClient.setContentType("application/pdf");
// Important:
// This is the decoded PDF binary, not the original base64 string.
uploadClient.setContent(pdfBinary);
const uploadRes = uploadClient.put(uploadUrl);
if (!uploadRes || uploadRes.isOk() === false) {
throw new Error("PDF upload failed: " + JSON.stringify(uploadRes));
}
let uploadData = uploadRes.response;
if (typeof uploadData === "string") {
uploadData = JSON.parse(uploadData);
}
if (!uploadData || !uploadData.id) {
throw new Error("Upload did not return file id: " + JSON.stringify(uploadData));
}
const fileId = uploadData.id;
// -----------------------------
// 4) Create sharing link
// -----------------------------
const linkUrl =
"https://graph.microsoft.com/v1.0/users/" +
encodeURIComponent(USER_UPN) +
"/drive/items/" +
encodeURIComponent(fileId) +
"/createLink";
const linkClient = five.httpClient();
linkClient.addHeader("Authorization", "Bearer " + accessToken);
linkClient.setContentType("application/json");
linkClient.setContent(
JSON.stringify({
type: "view",
scope: "anonymous"
})
);
let linkRes = linkClient.post(linkUrl);
let linkData = linkRes ? linkRes.response : null;
if (typeof linkData === "string") {
linkData = JSON.parse(linkData);
}
// If anonymous links are blocked, try organisation link
if (!linkData || !linkData.link || !linkData.link.webUrl) {
five.log("Anonymous link failed. Trying organization link.");
const orgLinkClient = five.httpClient();
orgLinkClient.addHeader("Authorization", "Bearer " + accessToken);
orgLinkClient.setContentType("application/json");
orgLinkClient.setContent(
JSON.stringify({
type: "view",
scope: "organization"
})
);
linkRes = orgLinkClient.post(linkUrl);
if (!linkRes || linkRes.isOk() === false) {
throw new Error("Create link failed: " + JSON.stringify(linkRes));
}
linkData = linkRes.response;
if (typeof linkData === "string") {
linkData = JSON.parse(linkData);
}
}
if (!linkData || !linkData.link || !linkData.link.webUrl) {
throw new Error("No share link returned: " + JSON.stringify(linkData));
}
const downloadUrl = linkData.link.webUrl;
five.log("PDF uploaded successfully: " + fileName);
five.log("Download URL: " + downloadUrl);
// -----------------------------
// 5) Return result to Five
// -----------------------------
result = five.success(result);
result.Results = JSON.stringify({
ok: true,
fileName: fileName,
fileId: fileId,
downloadUrl: downloadUrl
});
result.Code = five.ErrErrorOk;
result.Message = "";
return result;
}
Regards,
Elton S