OfficeDev / TeamsFx

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

SSO Authentication in Teams Application #7076

Closed acomvan closed 1 year ago

acomvan commented 1 year ago

Describe the bug The bug regards a SSO enabled Teams Application. The App signs the user in using the SSO and then redirects to another Web page, that then authenticates the user with the current Microsoft Login Session. But it is inconsistent. The problem occurs for some after a couple of hours and for some only after some weeks. When the issue occurs, the SSO enabled Tab authenticates the user and then the page is redirected. But for some reason, the right Cookies are not set and therefore the external web page redirects to the Microsoft Login Page, that can't be displayed in an iframe and therefore the user ends up with a blank page.

To Reproduce Steps to reproduce the behavior:

  1. Create a SSO enabled tab
  2. In the reload function, set window.location.href to a web page, that uses the Microsoft SSO
  3. Now install the application in Teams and run the application
  4. The first time the authentication in the external webpage should work properly
  5. After some time, you got the blank page

Expected behavior The external webpage, to which we redirect to, should always be able to authenticate the user.

VS Code Extension Information (please complete the following information):

Additional context This are the relevant parts of the reload function

const { loading, error, data, reload } = useGraph(

    async (graph, teamsfx, scope) => {

      // Call graph api directly to get user profile information
      const profile = await graph.api("/me").get();

      console.log(profile);

      ....

      window.location.href = link;

    },
    { scope: ["User.Read"], teamsfx: teamsfx }
  );
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.

KennethBWSong commented 1 year ago

Hi @acomvan Thank you for using our Toolkit. We are using msal-browser for authentication, and this package will save necessary info in session storage. Would you please try to use Dev Tools to check whether these settings correctly saved in your page's session storage?

chulla commented 1 year ago

Hi KennethBWSong, could you explain how to use msal-browser with teamsfx in a Microsoft teams tab app? Thx

acomvan commented 1 year ago

Hi @KennethBWSong, I checked it and the page has informtion for localStorage and some Cookies set. But in sessionStorage there are no values. In the authEnd and authStart files, I have the cache location set to sessionStorage. How could it be, that localStorage is used instead of sessionStorage? Thank you grafik

acomvan commented 1 year ago

Auth-start.html script code:

<script type="text/javascript">
      microsoftTeams.initialize();

      // Get the tab context, and use the information to navigate to Azure AD login page
      microsoftTeams.getContext(async function (context) {
        // Generate random state string and store it, so we can verify it in the callback
        var currentURL = new URL(window.location);
        var clientId = currentURL.searchParams.get("clientId");
        var scope = currentURL.searchParams.get("scope");
        var loginHint = currentURL.searchParams.get("loginHint");

        const msalConfig = {
          auth: {
            clientId: clientId,
            authority: `https://login.microsoftonline.com/${context.tid}`,
            navigateToLoginRequestUrl: false
          },
          cache: {
            cacheLocation: "sessionStorage",
            storeAuthStateInCookie: true
          },
        }

        const msalInstance = new msal.PublicClientApplication(msalConfig);
        const scopesArray = scope.split(" ");
        const scopesRequest = {
          scopes: scopesArray,
          redirectUri: window.location.origin + `/auth-end.html?clientId=${clientId}`,
          loginHint: loginHint
        };
        await msalInstance.loginRedirect(scopesRequest);
      });
      </script>

Auth-end.html script code:

<script type="text/javascript">
      var currentURL = new URL(window.location);
      var clientId = currentURL.searchParams.get("clientId");

      microsoftTeams.initialize();
      microsoftTeams.getContext(async function (context) {
        const msalConfig = {
          auth: {
            clientId: clientId,
            authority: `https://login.microsoftonline.com/${context.tid}`,
            navigateToLoginRequestUrl: false
          },
          cache: {
            cacheLocation: "sessionStorage",
          },
        }

        const msalInstance = new window.msal.PublicClientApplication(msalConfig);
        msalInstance.handleRedirectPromise()
          .then((tokenResponse) => {
            if (tokenResponse !== null) {
              microsoftTeams.authentication.notifySuccess(JSON.stringify({
                sessionStorage: sessionStorage
              }));
            } else {
              microsoftTeams.authentication.notifyFailure("Get empty response.");
            }
          })
          .catch((error) => {
            microsoftTeams.authentication.notifyFailure(JSON.stringify(error));
          });
      })
    </script>
KennethBWSong commented 1 year ago

Hi @acomvan Sorry for late reply. From your code we can say that the msal configs are correctly set to your session storage. Would you please take a look at the following storage? image

acomvan commented 1 year ago

Hi @KennethBWSong , I thought it is empty, because there are no entries on the right side table. For example if I select the localStorage, ther are a lot of entries on the righ side. Your requested storage looks exactly the same as the previous screenshot about teams storage. And for your information, the second storage is generated by the page we redirect to.

Thank you

KennethBWSong commented 1 year ago

Hi @acomvan Actually the authentication info is stored in the session storage for your tab instead of your Teams page. You won't find any data for sso in this session storage. Here is a screenshot for a simple template: image

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.

acomvan commented 1 year ago

Hi I don't actually understand what you mean by 'My Tab' instead of 'Teams Page' If I look in the session storage, the authentication infos are not stored there.

More information: What the tab does is to authenticate the user and then the page is redirected to another page. And after the redirection, the authentication infos are no where found in the session storage.

Is this the intended behaviour?

Thank you

KennethBWSong commented 1 year ago

Hi @acomvan Sorry for late reply. As you have provided before, the first time after trigger 'login' button, your Tab app works properly, and thus I think that tokens exist in your session storage at that time. After some time, the tokens expire, and graph calls fail, and the error should be caught in useGraph() here and trigger login again. I want to make sure whether the login flow is correctly triggered. Would you please have a try whether the profile can be successfully printed? image

acomvan commented 1 year ago

Hi @KennethBWSong This screenshot describes the unexpected behavior best. You can see that the login is done in teams and that's why information about the user is printed. After redirecting to another website, the user is still redirected back to the Microsoft login page. And since you can't display it in an iframe, the user only gets a blank screen. That's why I think the cookies are not set properly. And this happens randomly. For some users it never happens, for some it happens once a month and for some it has never worked properly.

I hope you can help me with this. Thank you very much. grafik

acomvan commented 1 year ago

Today I had access to the system of the same user who took the last screenshot. And there is no information stored in localStorage.

So in summary. For all other users, nothing is saved in SessionStorage but in localStorage and it works for these users. And for the user for whom it doesn't work, nothing is saved (localStorage and sessionStorage).

In the config, sessionStorage is set as the storage medium.

KennethBWSong commented 1 year ago

Hi @acomvan, seems I still cannot repro this error on my side. I want to first confirm that does your link (your website that redirected to) under the same domain as your Tab app? Also, would you please provide a minimum sample (not your real project) that can repro this issue?

acomvan commented 1 year ago

Hi @KennethBWSong the website we redirect to is not on the same domain as our teams application. But it is a page that uses Microsoft SSO And like I said, for most users and most of the time it works. But for some all the time and for others rarely, it doesn't work.

My understanding is, that every time I authenticate the user, also the cookies are updated. Then after redirection to our website, the page detects the active Microsoft Session through the cookie and authenticates the user.

Is my understanding of the procedure right, or is the creation of the cookie only a side product of the Microsoft Login Procedure and it is not assured that at all times the cookies are also up to date?

I will attach a minimal version of the main tab code. This takes care of the authentication and redirection.

export default function Tab() {
  const { teamsfx } = useContext(TeamsFxContext);
  let link = 'this is the link of our website';

  // eslint-disable-next-line
  const { loading, error, data, reload } = useGraph(
    async (graph, teamsfx, scope) => {
      // Call graph api directly to get user profile information
      const profile = await graph.api("/me").get();
      console.log(profile);

      // Initialize Graph Toolkit TeamsFx provider
      const provider = new TeamsFxProvider(teamsfx, scope);
      Providers.globalProvider = provider;
      Providers.globalProvider.setState(ProviderState.SignedIn);
    },
    { scope: ["User.Read"], teamsfx: teamsfx }
  );

  useEffect(() => {
    if (!loading) {
      if (typeof Providers.globalProvider === 'undefined') {
        reload();
      } else {
        //only here redirect to our website
        window.location.href = link;
      }
    }
  }, [loading])

  const { themeString } = useContext(TeamsFxContext);

  return (
    <div className={themeString === "default" ? "" : "dark"}>
    </div>
  );
}

I already set the storeAuthStateInCookie: true , but the problem remains.

KennethBWSong commented 1 year ago

Hi @acomvan, actually we have more concern on how you implement SSO in your external website. Would you please provide a minimum sample on this? Also, here are some suggestions:

  1. Add your external website to validDomains in your Teams app manifest;
  2. Make sure your msal config is the same in auth-start.html and auth-end.html
  3. Would you please also have a try to update the cache location to localStorage as described here in msalConfig in both auth-start.html and auth-end.html like: image
acomvan commented 1 year ago

Hi @KennethBWSong thank you for your response.

Regarding the external website, someone else developed it. I will get in touch with them.

KennethBWSong commented 1 year ago

Hi @acomvan Thanks! Code of external website will help us better troubleshooting your problem. If possible, would you please also print out values of session storage after printing out the profile to see whether msal-browser can successfully store your credential to session storage. Sample code:

var storage = window.sessionStorage;
for(var i=0, len=storage.length; i<len;i++){
    var key = storage.key(i);    
    var value = storage.getItem(key);    
    console.log(key + "=" + value);
}
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.

acomvan commented 1 year ago

Hi @KennethBWSong , I have the output of the session storage. It seems that everything is right there. In the Session Storage there is

Could you say that the problem is in the external application? And is there a guide to implement the Microsoft Login, so that it works with Micrsoft Teams App? Because so far in browsers everything worked fine with the external website and Mcirosoft SSO.

grafik

KennethBWSong commented 1 year ago

Hi @acomvan As all the things is correctly set in session storage, we would like to believe that TeamsFx SDK has successfully got the access token. It would help us a lot to debug if you can provide a sample piece of code of your external website.

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.