OfficeDev / TeamsFx

Developer tools for building Teams apps
Other
427 stars 165 forks source link

teamsUserCredential.getToken Getting UiRequiredError in Teams app - works in browser #7808

Closed tnsholding closed 1 year ago

tnsholding commented 1 year ago

Debugging a simple Teams SSO app started from the standard Teams Toolkit sample.

When debugging through Teams in the browser I can make graph calls just fine, but when running the Teams client I get "ErrorWithCode.UiRequiredError" error.

Steps to reproduce the behavior:

  1. Start from standard Teams app template from Teams Toolkit
  2. Modify app.tsx to code below
  3. Debug in VSCode
  4. Accesstoken is shown in browser
  5. Error: "ErrorWithCode.UiRequiredError" is shown in Teams client

Expected behavior I would expect the Teams browser and Teams client to behave exactly the same.

// https://fluentsite.z22.web.core.windows.net/quick-start
import { Provider, teamsTheme, Loader } from "@fluentui/react-northstar";
import { HashRouter as Router, Redirect, Route } from "react-router-dom";
import { useTeamsUserCredential } from "@microsoft/teamsfx-react";
import Privacy from "./Privacy";
import TermsOfUse from "./TermsOfUse";
import Tab from "./Tab";
import "./App.css";
import TabConfig from "./TabConfig";
import { TeamsFxContext } from "./Context";
import config from "./sample/lib/config";
import { useEffect, useState } from "react";

/**
 * The main app which handles the initialization and routing
 * of the app.
 */
export default function App() {
  const { loading, theme, themeString, teamsUserCredential } = useTeamsUserCredential({
    initiateLoginEndpoint: config.initiateLoginEndpoint!,
    clientId: config.clientId!,
  });
    const [token, settoken] = useState('');

  useEffect(() => {
    (async () => {
      if(teamsUserCredential) {
        try {
          const test = await teamsUserCredential.getToken(["User.Read"]);
          settoken(test?.token??'');
        } catch (error) {
          settoken(JSON.stringify(error));
        }
      }
    })();
  }, [teamsUserCredential])
  return (
    <TeamsFxContext.Provider value={{theme, themeString, teamsUserCredential}}>
      <Provider theme={theme || teamsTheme} styles={{ backgroundColor: "#eeeeee" }}>
        <Router>
          <Route exact path="/">
            <Redirect to="/tab" />
          </Route>
          {loading ? (
            <Loader style={{ margin: 100 }} />
          ) : (
            <>
            <h1>{token}</h1>
              <Route exact path="/privacy" component={Privacy} />
              <Route exact path="/termsofuse" component={TermsOfUse} />
              <Route exact path="/tab" component={Tab} />
              <Route exact path="/config" component={TabConfig} />
            </>
          )}
        </Router>
      </Provider>
      </TeamsFxContext.Provider>
  );
}
ghost commented 1 year ago

Thank you for contacting us! Any issue or feedback from you is quite important to us. We will do our best to fully respond to your issue as soon as possible. Sometimes additional investigations may be needed, we will usually get back to you within 2 days by adding comments to this issue. Please stay tuned.

tnsholding commented 1 year ago

Additional information:

I have granted admin consent manually through Azure Active Directory. This seems to work just fine in the browser and I can get an access token without clicking the "Authorize" button from the standard sample.

But in Teams client I'm still required to click the "Authorize" button and consent as an individual user, why? - shouldn't the admin consent granted through AAD be enough?

blackchoey commented 1 year ago

@tnsholding It's recommended to add logic to login users interactively when you receive UiRequiredError. And here're the technical details:

TeamsUserCredential uses AAD authorization code flow to get user's access token, with one extra optimization: if there's no cached access token, it will try to use the MSAL library's ssoSilent() feature to login current user silently. So your application is able to not ask users login when ssoSilent() works, which provides better user experience. This is why you don't need to login in browser.

However, ssoSilent() does not work in every case (e.g. https://learn.microsoft.com/en-us/azure/active-directory/develop/msal-js-sso#considerations-when-using-ssosilent describes some situations). When TeamsUserCredential cannot acquire a token from cache or via ssoSilent(), it will throw UiRequiredError to let you know that you need to login users interactively.

We will investigate why this does not work in Teams desktop client. Since I'm not able to repro this issue in our tenant, can you share @microsoft/teamsfx related logs from browser dev tool -> Console? You can remove any sensitive information in it. There should be detailed reason about why ssoSilent() does not work in the logs.

tnsholding commented 1 year ago

This is an already deployed Teams app, that I'm just trying to upgrade to the latest version of TeamFx. I would want to avoid having thousands of users suddenly click a new login button and authenticate. Everything works fine in the old version.

I don't think the logs from browser dev tools would give anything useful? - as everything works just fine in the browser. It's only in the desktop client the issue occurs.

blackchoey commented 1 year ago

I don't think the logs from browser dev tools would give anything useful? - as everything works just fine in the browser. It's only in the desktop client the issue occurs. Sorry, I mean the dev tools for Teams desktop client: https://learn.microsoft.com/en-us/microsoftteams/platform/tabs/how-to/developer-tools#access-devtools-on-the-desktop. The SDK will print some log by default.

I'm just trying to upgrade to the latest version of TeamFx do you mean you're trying to upgrade to latest @microsoft/teamsfx package? If yes, can you share the old version used in your project?

tnsholding commented 1 year ago

Doing a refactoring and upgrading - specifically:

And adding: "@microsoft/teamsfx-react": "^2.1.1",

I'm struggling with getting output from dev tools from Teams desktop client. I have the following options in the tray icon, and tried opening all of them. But none seem to output the actual traces from the add-in? I tried adding a few "console.log" statements in the code. These show up in Dev tools in browser, but can't seem to get these output from the Desktop dev tools.

Tray icon dev tools options: image

Output from "Open DevTools (main window)" when loading the tab: image

blackchoey commented 1 year ago

Thank you. There was an update previously that changes Tab app authentication method to auth code flow with PKCE in the SDK, which brings extra security to tab app. Please refer the release note popped up after Teams Toolkit extension update here: https://github.com/OfficeDev/TeamsFx/blob/dev/packages/vscode-extension/WHATISNEW.md#330---feb-07-2022. More details can be found at https://github.com/OfficeDev/TeamsFx/wiki/How-Authentication-Works-in-TeamsFx-Tab-Template.

Since you do not want to ask users login, the recommended way is building a backend API that calls Microsoft Graph or other APIs from server side. So your tab app just need to acquire a SSO token (used to call your backend API), which is provided by Teams directly. Since there's no need to acquire a Microsoft Graph (or other service's) token, your tab app does not need to ask users login any more.

tnsholding commented 1 year ago

Thanks for the info. But still puzzled why the tab behaves differently in the browser and in the desktop client. It just works in the browser.

And would really like to avoid going through server side for all graph calls - my app has a lot of graph calls and would require lot of refactoring of code as well as additional traffic on all requests.

blackchoey commented 1 year ago

As mentioned previously, we use the ssoSilent() function from MSAL library to optimize login experience (reduce the requirement of login as much as possible). But ssoSilent() does not guarantee it works under every situation. It should meet some problems when trying to login your users in Teams desktop client.

You can follow these steps to open devtools for your tab app in Teams desktop client to find the logs: open your app in Teams desktop client -> right click Teams icon -> DevTools -> Open DevTools (Select Webcontents) -> webview.

You can share your error logs here so we can see is there any recommendation we can give.

ghost commented 1 year ago

This issue has been automatically marked as stale because it has been marked as requiring author feedback but has not had any activity for 7 days. It will be closed if no further activity occurs within 3 days of this comment. If it is closed, feel free to comment when you are able to provide the additional information and we will re-investigate.

tnsholding commented 1 year ago

This is the only output from the console from Teams Desktop dev tools:

image

Where would I get/setup additional debug info for MSAL library?

blackchoey commented 1 year ago

Seems it's the log for Teams desktop client. Can you double check whether you selected webview in below steps? open your app in Teams desktop client -> right click Teams icon -> DevTools -> Open DevTools (Select Webcontents) -> webview

Usually there's only 1 webview which represents the tab app. Here're some sample logs: image

tnsholding commented 1 year ago

I do not have the option "webView" - the only options are: image

Choosing 'mainWindow' gives the following - apparently no info from msal:

image

blackchoey commented 1 year ago

@hund030 @tecton can you help see how can tnsholding find the logs of tab app?

@tnsholding The purpose of collecting the error messages is helping you understand why MSAL cannot login user silently in your tenant. We can see whether there's any additional recommendation we can provide but probably no way to workaround this from security perspective. Hope you're not depending on this to continue your development. Please choose either way recommended below for your app.

  1. Use auth code flow with pkce in your tab app to get a Microsoft Graph token and call the API in tab app directly. This may require user login when MSAL cannot login the user silently.

    1. Create a backend API which acquires Microsoft Graph token using OBO flow and call the API in backend. This does not require user login if you enabled SSO for the tab app and do admin consent for the required permissions.
tnsholding commented 1 year ago

But forgetting about the logs - and going back to the original question: Why is it possible to get SSO to work without logging in when using the browser-based version of Teams, but not the Teams client? It's a very simple issue to reproduce using the steps in the original post.

blackchoey commented 1 year ago

@tnsholding Please see the document I shared previously, the ssoSilent() function does not always work and the document gives a possible situation that it won't work. The logs we're trying to collect will help you understand why ssoSilent() doesn't work for you.

For the repro, it should relate to your environment, and I cannot repro it in my side. So we need your help if you want to figure out why ssoSilent() doesn't work to see is there any workaround. I'm working internally to see why you cannot find the logs.

I fully understand your situation due to the change in preview stage. Since the new way provides better security for your app, you can consider whether to adopt the new approaches. You can also refer tab authentication best practices for more information.

If you really want to adopt the previous way, you can host the simple auth server (source) and make requests to it to get MS Graph access token by yourself. Please kindly notice the simple auth server is no longer maintained and you need to patch it if any vulnerability is found.

ghost commented 1 year ago

This issue has been automatically marked as stale because it has been marked as requiring author feedback but has not had any activity for 7 days. It will be closed if no further activity occurs within 3 days of this comment. If it is closed, feel free to comment when you are able to provide the additional information and we will re-investigate.

ghost commented 1 year ago

Due to lack of details for further investigation, we will archive the issue for now. In case you still have following-up questions on this issue, please always feel free to reopen the issue by clicking ‘reopen issue’ button below the comment box. We will get back to you as soon as possible.