OneDrive / onedrive-api-docs

Official documentation for the OneDrive API
MIT License
458 stars 232 forks source link

Onedrive picker not working when auth using azure and msal #1422

Closed jcharnley closed 3 years ago

jcharnley commented 3 years ago

Category

Expected or Desired Behavior

Hi

Library

We have two auth flows in our application, one is a onedrive file picker and the another is aauth flow using msal to login to ms graph API. When I login via ms graph API via msal it works great but it seem to have broken the onedrive picker auth flow.

The error is:

RequestUtils.js:167 Uncaught ClientAuthError: Invalid state. http://localhost:8080_H4WzD, state expected : null. at ClientAuthError.AuthError [as constructor] (webpack:///./node_modules/msal/lib-es6/error/AuthError.js?:26:28) at new ClientAuthError (webpack:///./node_modules/msal/lib-es6/error/ClientAuthError.js?:106:28) at Function.ClientAuthError.createInvalidStateError (webpack:///./node_modules/msal/lib-es6/error/ClientAuthError.js?:136:16) at Function.RequestUtils.parseLibraryState (webpack:///./node_modules/msal/lib-es6/utils/RequestUtils.js?:167:90) at UserAgentApplication.getResponseState (webpack:///./node_modules/msal/lib-es6/UserAgentApplication.js?:1148:97) at new UserAgentApplication (webpack:///./node_modules/msal/lib-es6/UserAgentApplication.js?:127:34) at eval (webpack:///./src/components/auth-sharepoint/authPopup.js?:22:19) at Module../src/components/auth-sharepoint/authPopup.js (http://localhost:8080/app.bundle.js:957:1) at webpack_require (http://localhost:8080/manifest.bundle.js:850:30) at fn (http://localhost:8080/manifest.bundle.js:152:20)

Current Flow

User can login via msal to download sharepoint files on load via query paremeter link, that loads into babylon js viewer User can login to onedrive picker SDK and pick the file they want to load into the babylong js viewer, not via the query par.

I've read that they may be incompatable token due to them requesting one from different API's, however I was wondering if I was able to add the token manually and update the endpointHint within the onedrive open config that will allow the flow to use the msal auth.

Anyone got any ideas, any help would be great

thanks

Source

authConfig.js

const msalConfig = {
  auth: {
    clientId: "id",
    authority: "https://login.microsoftonline.com/common",
    redirectUri: "http://localhost:8080"
  },
  cache: {
    cacheLocation: "sessionStorage", // This configures where your cache will be stored
    storeAuthStateInCookie: false // Set this to "true" if you are having issues on IE11 or Edge
  }
};

// Add scopes here for ID token to be used at Microsoft identity platform endpoints.
const loginRequest = {
  scopes: ["openid", "profile", "User.Read"]
};

// Add scopes here for access token to be used at Microsoft Graph API endpoints.
const tokenRequest = {
  scopes: ["User.Read", "Mail.Read"]
};

module.exports = { msalConfig, loginRequest, tokenRequest };

authPopup.js

import * as Msal from "msal";
import { load3dModel } from "../../index";
import {fetchProgress} from '../../fetch-api-progress';

import { msalConfig, loginRequest, tokenRequest } from "./authConfig";
import { callMSGraph } from "./graph";
import { graphConfig } from "./graphConfig";

const myMSALObj = new Msal.UserAgentApplication(msalConfig);

let username = "";
let encodedString = "";

function handleResponse(resp) {
  if (resp !== null) {
    username = resp.account.userName
    document.getElementById("welcomeMessage").textContent = "Welcome " + username;
    getSharedUrl(encodedString);
  } else {
    // loadPage();
  }
}

export const signIn = (encodedStringInput) => {
    encodedString = encodedStringInput;
  myMSALObj
    .loginPopup(loginRequest)
    .then(handleResponse)
    .catch((error) => {
      console.error(error);
    });
};

// export const signOut = () => {
//   const logoutRequest = {
//     account: myMSALObj.getAccountByUsername(username)
//   };
//   myMSALObj.logout(logoutRequest);
// }

function getTokenPopup(request) {
  /**
   * See here for more info on account retrieval:
   * https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-common/docs/Accounts.md
   */
  return myMSALObj.acquireTokenSilent(request).catch((error) => {
    console.warn(
      "silent token acquisition fails. acquiring token using redirect"
    );
    if (error instanceof msal.InteractionRequiredAuthError) {
      // fallback to interaction when silent call fails
      return myMSALObj
        .acquireTokenPopup(request)
        .then((tokenResponse) => {
          return tokenResponse;
        })
        .catch((error) => {
          console.error(error);
        });
    } else {
      console.warn(error);
    }
  });
}

const getSharedUrl = async () => {
    getTokenPopup(loginRequest).then(response => {     
        const url = `https://graph.microsoft.com/v1.0/shares/u!${encodedString}/driveItem`
        callMSGraph(url, response.accessToken, load3dModel, fetchProgress);
    }).catch(error => {
        console.error(error);
    });
};

onedrive.js

function readBlob(blob, callback) {
  if (blob) {
    let reader = new FileReader();

    reader.readAsArrayBuffer(blob);

    reader.onload = function (e) {
      const arrayBuffer = reader.result;
      const blob = new Blob([arrayBuffer], {
        type: "text/plain;charset=utf-8"
      });
      let src = URL.createObjectURL(blob);
      callback(src);
    };
    reader.onerror = function () {
      return alert("Failed to load file");
    };
  }
}

//Reads file from file cloud url and returns blob
async function readCloudFile(res, callback) {
  let url = null;
  if (res.value.length > 0) {
    console.log("res.value", res.value)
    url = res.value[0]["@microsoft.graph.downloadUrl"];
  }
  if (url) {
    let response = await fetch(url);

    const reader = response.body.getReader();
    const contentLength = +response.headers.get('Content-Length');

    let receivedLength = 0; // received that many bytes at the moment
    let chunks = []; // array of received binary chunks (comprises the body)
    while(true) {
      const {done, value} = await reader.read();

      if (done) {
        break;
      }
      chunks.push(value);
      receivedLength += value.length;
      let receivedLengthMB = (receivedLength / (1024*1024)).toFixed(2);
      let totalLengthMB =  (contentLength / (1024*1024)).toFixed(2);
      document.getElementById("onedriveLoaderProgress").textContent = `Received ${receivedLengthMB}MB of ${totalLengthMB}MB`
      // console.log(`Received ${receivedLengthMB}MB of ${totalLengthMB}MB`)

    }
    let chunksAll = new Uint8Array(receivedLength); // (4.1)
    let position = 0;
    for(let chunk of chunks) {
      chunksAll.set(chunk, position); // (4.2)
      position += chunk.length;
    }

    let blob = new Blob([chunksAll]);
    readBlob(blob, callback);
  } else {
    alert("File URL not valid");
  }
  // const length = response.headers.get('Content-Length');
}

//sets up connection with onedrive and opens filepicker, then actions file post-process.
function loadOneDriveConnection(callback) {

  if (window.navigator.onLine) {
    //App id from Azure App
    const oneDriveApplicationId = "id";

    //One Drive Picker Options
    let odOptions = {
      clientId: oneDriveApplicationId,
      action: "download",
      multiSelect: false,
      advanced: {
      },
      openInNewWindow: false,

      success: function (files) {
        //On success, load in blob to file reader
        readCloudFile(files, callback).catch((err) =>
          console.log("Error Reading Cloud File: " + err)
        );
      },
      cancel: function () {
        /* cancel handler */
      },
      error: function (e) {
        /* error handler */ alert("OneDrive Error: " + e);
      }
    };

    OneDrive.open(odOptions);
  } else {
    alert("Application offline, cannot load OneDrive");
  }
}

module.exports = { loadOneDriveConnection };
ghost commented 3 years ago

Thank you for your contribution to OneDrive API Docs. We will be triaging your incoming issue as soon as possible.