Search code examples
microsoft-teamsmsal.jsteams-toolkitmicrosoft-teams-js

[email protected] teamsUserCredential.getToken(scopes) results in login required error


I am using the following packages in my Teams React JSX SSO app

"name": "@microsoft/teamsfx-react", 
"version": "3.1.3"
"name": "@microsoft/teamsfx",
"version": "2.3.3"
"name": "@azure/msal-browser"
"version": "3.27.0",

On Teams desktop, the client requests for credentials even if session is logged in. I have set the redirect URI's as following on the Azure portal. All the url's are accessible Azure AAD app Redirect URI

The scope has been configured as below

AAD Scope

Below is the code for my auth-start.html

<html>
  <head>
    <title>Login Start Page</title>
    <meta charset="utf-8" />
  </head>

  <body>
    <script
      src="https://res.cdn.office.net/teams-js/2.31.1/js/MicrosoftTeams.min.js"
      integrity="sha384-ihAqYgEJz9hzEU+HaYodG1aTzjebC/wKXQi1nWKZG7OLAUyOL9ZrzD/SfZu79Jeu"
      crossorigin="anonymous"
    ></script>
    <script
      async
      type="text/javascript"
      src="https://example.com/ui/static/js/msal-browser.min.js">
    </script>
    <script type="text/javascript">
      microsoftTeams.app.initialize().then(() => {
        microsoftTeams.app.getContext().then(async (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.user.tenant.id}`,
              navigateToLoginRequestUrl: false,
              redirectUri: window.location.origin + '/ui/blank-auth-end.html',
            },
            cache: {
              cacheLocation: "sessionStorage",
            },
          };

          const msalInstance = new msal.PublicClientApplication(msalConfig);
          await msalInstance.initialize();
          const scopesArray = scope.split(" ");
          const scopesRequest = {
            scopes: scopesArray,
            redirectUri: window.location.origin + `/ui/auth-end.html?clientId=${clientId}`,
            loginHint: loginHint,
          };
          
          // Handle the login redirect
          try {
            await msalInstance.loginRedirect(scopesRequest);
          } catch (error) {
            console.error("Login redirect failed: ", error);
          }
        });
      });
    </script>
  </body>
</html>

Below is the code for my auth-end.html

<!--This file is used during the Teams authentication flow to assist with retrieval of the access token.-->
<!--If you're not familiar with this, do not alter or remove this file from your project.-->
<html>
  <head>
    <title>Login End Page</title>
    <meta charset="utf-8" />
  </head>

  <body>
    <script
      src="https://res.cdn.office.net/teams-js/2.31.1/js/MicrosoftTeams.min.js"
      integrity="sha384-ihAqYgEJz9hzEU+HaYodG1aTzjebC/wKXQi1nWKZG7OLAUyOL9ZrzD/SfZu79Jeu"
      crossorigin="anonymous"
    ></script>
    <script
      async
      type="text/javascript"
      src="https://example.com/ui/static/js/msal-browser.min.js">
    </script>
    <script type="text/javascript">
      var currentURL = new URL(window.location);
      var clientId = currentURL.searchParams.get("clientId");

      microsoftTeams.app.initialize().then(() => {
        microsoftTeams.app.getContext().then(async (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);
          await msalInstance.initialize();
          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>
  </body>
</html>

On a Chrome Edge browser the start page and end page pops up but does not ask for credentials and provides access to my page but on Teams desktop app it requests for credentials every time I close and restart the Teams client. I am using the token to authenticate with my GO backend. I followed instructions in [email protected] TeamsUserCredential.getToken results in login required error to match the version of msal-browser package in my react app with the html page's msal-browser script. Since Microsoft no longer provides CDN for version 3.27.0, I downloaded it from NPM package and am hosting it from my url. I am hoping I do not have to rollback to an older version to get the SSO working.

####################################################################

Update: I was able to resolve the issue. The issue was due to the scope for getToken() request. I set the scope to default ("[]") and the SSO worked as expected. I updated the code to below and now the SSO is working as expected.

const {loading,error,data: teamsToken, reload} = useData(async () => {
      if (!teamsUserCredential){
        setTokenError("No Teams User Credential found");
        return;
      }

      try{
        const teamsToken = await teamsUserCredential.getToken("");
        setTokenError(null);
        return teamsToken;
      }catch(error){
        if (error.code === "UiRequiredError"){
          try{
            const scopes = ["access_as_user"];
            await teamsUserCredential.login(scopes);
            const teamsToken = await teamsUserCredential.getToken("");
            setTokenError(null);
            navigate("/tab");
            return teamsToken;
          }catch(loginError){
            console.error("Error during login: ", loginError);
            throw loginError;
          }
        }else{
          console.error("Error fetching token after login error: ",error);
          throw error;
        }
      }
    })

    useEffect(() => {
      if (teamsToken) {
        setToken(teamsToken);
      }
      if (error) {
        setTokenError("Failed to fetch token. Please ensure the pop-up blocker is disabled for this site and refresh the page.");
      }
      setTokenLoading(loading);
    }, [teamsToken, error, loading]);
    


Solution

  • I was able to resolve the issue. The issue was due to the scope for getToken() request. I set the scope to default ("[]") and the SSO worked as expected. I updated the code to below, and now the SSO is working as expected.

    const {loading,error,data: teamsToken, reload} = useData(async () => {
          if (!teamsUserCredential){
            setTokenError("No Teams User Credential found");
            return;
          }
    
          try{
            const teamsToken = await teamsUserCredential.getToken("");
            setTokenError(null);
            return teamsToken;
          }catch(error){
            if (error.code === "UiRequiredError"){
              try{
                const scopes = ["access_as_user"];
                await teamsUserCredential.login(scopes);
                const teamsToken = await teamsUserCredential.getToken("");
                setTokenError(null);
                navigate("/tab");
                return teamsToken;
              }catch(loginError){
                console.error("Error during login: ", loginError);
                throw loginError;
              }
            }else{
              console.error("Error fetching token after login error: ",error);
              throw error;
            }
          }
        })
    
        useEffect(() => {
          if (teamsToken) {
            setToken(teamsToken);
          }
          if (error) {
            setTokenError("Failed to fetch token. Please ensure the pop-up blocker is disabled for this site and refresh the page.");
          }
          setTokenLoading(loading);
        }, [teamsToken, error, loading]);