OfficeDev / Office-Add-in-samples

Code samples for Office Add-in development on the Microsoft 365 platform.
MIT License
709 stars 780 forks source link

How can I get access token for multiple resources based on sample Office-Add-in-Microsoft-Graph-React #588

Open tiwarigaurav opened 1 year ago

tiwarigaurav commented 1 year ago

Note: This repo is only for questions related to its samples. If you have questions about how to use office.js or the Office developer platform, please post your question on https://stackoverflow.com. Tag your question with office-js or outlook-web-addins

Question

Ask your question here.

I am developing an Outlook Web Add-In using

I followed the example from "Office-Add-in-Microsoft-Graph-React" and I am able to successfully get the access token for Graph API.

My question is how can I request access token for multiple resources using the msalInstance i.e. SharePoint, Onedrive.

Rick-Kirkham commented 1 year ago

@tiwarigaurav Please ask this question on on https://stackoverflow.com/. Tag your question with office-js or outlook-web-addins

This repo is only for questions related to its samples.

tiwarigaurav commented 1 year ago

@Rick-Kirkham - not sure why this was closed. My question specifically relates to the sample "Office-Add-in-Microsoft-Graph-React"

Using this sample I can get the Graph Access Token just fine and everything works.

My Question is - How can I get access token for multiple resources using the msalInstance.acquireTokenSilent(scope) within the office-apis-helpers.ts file?

The main idea is to use the login popup once to initiate the msalinstance and for any subsequent scopes/resources (or when the initial access token expires) use the acquireTokenSilent method - so that the user does not need to click the login button again to get another token.

tiwarigaurav commented 1 year ago

@davidchesnut Please let me know if you need more details in investigating our issue. We anticipate quick response and resolution to above.

Rick-Kirkham commented 1 year ago

@tiwarigaurav I leave it to @davidchesnut to decide, but I don't think this "relates" to the sample in the way that is intended. The sample doesn't have a bug and your question is not about the sample as it is. You are asking for help for a distinctly different scenario. You really should ask this question on Stack Overflow.

tiwarigaurav commented 1 year ago

@Rick-Kirkham - what I am trying to imply is that the sample provided does not work in real-world scenarios, neither can we make it work in real world.

As per Microsoft documentation the -

Office dialog API, specifically the displayDialogAsync method is a completely separate browser instance from the task pane, meaning:

  • It has its own runtime environment and window object and global variables.
  • There is no shared execution environment with the task pane.
  • It does not share the same session storage (the Window.sessionStorage property) as the task pane.

Which means (my interpretation) is that we cannot use msalInstance.acquireTokenSilent(scope). And that every time we need to get a new access token we will need the user to click the login button.

With the above statement I can safely say that the sample provided under OfficeDev -> Office-Add-in-samples -> auth -> Office-Add-in-Microsoft-Graph-React cannot be used in real world (or the concept cannot be reused in real world). Unless I am missing something quite obvious which is why I have posted this question.

tiwarigaurav commented 1 year ago

@davidchesnut - any updates for us on this issue?

mattgeim commented 1 year ago

Hi @tiwarigaurav,

It appears you are looking for information on how to use MSAL in react to get additional scopes beyond the one used in this sample.

This sample in the Azure Active Directory documentation may be better aligned to help with your question. Specifically, the scopes are what you pass in to specify what API/scope of access you want granted in the token, which you can find in the 'Acquire token for an API' part of that sample. If you are still having issues with MSAL and accessing resources using it, the best spot to ask would be Stack Overflow using msal and msal.js tags.

Closing the issue on this sample as your question is about MSAL usage - if you find issues with MSAL.js you can more information on their GitHub page/open an issue there.

tiwarigaurav commented 1 year ago

@mattgeim - It is possibly my fault - the title of the question is a bit misleading. Let's take out the multiple scopes part out of the equation and concentrate on this sample - OfficeDev -> Office-Add-in-samples -> auth -> Office-Add-in-Microsoft-Graph-React

I am trying to imply is that the sample provided does not work in real-world scenarios, neither can we make it work in real world. i.e. every time we need to get a new access token we will need the user to click the login button and show the dialogue popup.

Perhaps this should be raised as a bug in the sample?

Note: from what I have gathered so far is that "Office Add-In + React + MSAL.JS + Graph API" will not work cross platform and cross browsers.

mattgeim commented 1 year ago

Thanks for the response clarifying @tiwarigaurav.

This sample doesn't do SSO, its MSAL based and isn't trying to reduce sign-ins. If you are interested in SPA flows for SSO, you can read more about it here: https://learn.microsoft.com/en-us/azure/active-directory/develop/msal-js-sso please note this is not related to add-ins, but rather SSO flows and MSAL.

This sample works for what it should do, but I will happily take your feedback and discuss with our team on ways to improve on clarity for others in the future - thank you for the feedback!

tiwarigaurav commented 1 year ago

@mattgeim - this isn't about SSO with Office Add-Ins.

My feedback is that this sample Office-Add-in-Microsoft-Graph-React will NOT work in real world. There is a bug with Office Add-Ins Dialogue API I found this on MS documentation.

This sample should clearly state this in on the wiki page.

davidchesnut commented 1 year ago

Hi @tiwarigaurav, If you want to use MSAL caching so that you don't have to pop up a dialog for every API call, see getAccessTokenMSAL in the Office-Add-in-NodeJS-SSO sample. You can also see how to send the home account ID from the dialog box in the handleResponse method. This is all MSAL code based on the Microsoft identity platform, and nothing specific to Office JS. In the comments for authConfig.js and authRedirect.js there are links to the original MSAL samples those are based on. However it is possible there are some Office platform/webview combinations that might not work with this MSAL caching mechanism (in which case it defaults back to popping up the auth dialog.)

I can look into updating the React sample down the road with the caching code. Are there any other issues you spotted in the sample?

Thanks! David

tiwarigaurav commented 1 year ago

@davidchesnut - thanks for responding. I tried this and it does NOT work.

I tried your suggestion with the below code (in my app's login.ts file):

     let msalInstance: PublicClientApplication = new PublicClientApplication({
      auth: {
      authority: "https://login.microsoftonline.com/organizations/",
      clientId: "xxxxxxx-xxxxxxx-xxxxxxx-xxxxxxx-xxxxxxx",
      navigateToLoginRequestUrl: false
      },
      cache: {
      cacheLocation: "localStorage",
      storeAuthStateInCookie: false,
      },
    });
    const authParamsGraph = {
      account: messageFromDialog.result.account,
      scopes: ["https://graph.microsoft.com/.default"]
    };
    console.log(messageFromDialog.result.account.homeAccountId);
    const accByHomeId = msalInstance.getAccountByHomeId(messageFromDialog.result.account.homeAccountId);
    console.log(accByHomeId);
    msalInstance.setActiveAccount(
      msalInstance.getAccountByHomeId(
        messageFromDialog.result.account.homeAccountId
      )
    );

With the above I was able to pass the homeAccountId from the Dialog box to the app but msalInstance.getAccountByHomeId(messageFromDialog.result.account.homeAccountId) returns null.

As per my research there is a bug with the Office Dialog API in which - the web add-in's Task Pane and Dialog Box do not share the same Local Storage. So, when we initialize a new msal instance in the app it has nothing in common with the msal instance from the Dialog box so the getAccountByHomeId will fail.

Note: I am testing this on Outlook desktop app running on Windows

davidchesnut commented 12 months ago

Hi @tiwarigaurav,

I did some more research into this. The Microsoft Graph React auth sample solves this problem by storing the token in a variable. See line 51 in App.tsx. I checked with the team, and in the context of SPA using implicit grant with PKCE, this is fine. This code pattern allows you to reuse the token on subsequent calls without needing to sign in again.

I noticed the sample had some out of date libraries and a minor typeScript issue. I fixed these in PR #605. So you should be able to use this sample once the PR goes through.

Hope this helps, David

tiwarigaurav commented 12 months ago

Hi @davidchesnut,

Thanks for taking the time to update this.

I might be missing something here OR I have been unable to explain the issue - please bare with me.

Getting the token back from the "displayDialogAsync" popup saving it in a variable in the App.tsx file and using it to access GraphAPI has been fine and is working as expected.

The issue that I am trying to get help on is:

  1. AccessTokens have a lifespan of about 75minutes - so after the token expires we need to get another token to call GraphAPI
  2. To get another AccessToken we need to use the msalInstance.acquireTokenSilent method of the msal-browser (we cannot expect the user to keep initiating the "displayDialogAsync" popup every hour)
  3. This is where the entire thing falls apart
  4. If we initialize a new msalInstance from the REACT app, this new instance does not have the context of the original msal instance that was used in the displayDialogAsync popup (login.html), hence the acquireTokenSilent fails

Trying to use the acquireTokenSilent from the REACT app fails with error: Exception has occurred: ClientAuthError: token_refresh_required: Cannot return token from cache because it must be refreshed. This may be due to one of the following reasons: forceRefresh parameter is set to true, claims have been requested, there is no cached access token or it is expired.

davidchesnut commented 10 months ago

Hi @tiwarigaurav, you are correct, and the token expires. I've been looking into this for any possible workarounds but it doesn't look like this is possible and you have to sign the user in again. This is definitely a pain point and I'll share this with the rest of the product team to look into.

The one approach that will work is using SSO. Office will refresh the token for you automatically if you request it through getAccessToken() and it has expired. If SSO works for your scenario I'd recommend using that approach.

Hope this helps, David

tiwarigaurav commented 10 months ago

@davidchesnut - thanks for acknowledging that there is an issue. I was thinking that I was going out of my mind ;)

SSO does not help - if we need to create cross browser/cross platform Add-Ins we need a fall back method (SSO will not work in Safari and other exceptions).

The only conclusion that I have reached is - msal-browser/msal-react does not work with Office web add-ins in real world scenarios and because of this, even msal-node OR msal .net will not work. If we need to implement a real world add-in we need to use OBO flow and manage access and refresh tokens ourselves i.e. using a DB.