首页 > 解决方案 > How to configure SSO for chatbot deployed on website

问题描述

I am trying to configure SSO using the below code.

  <!DOCTYPE html>
<html>
<head>
<title>Contoso Sample Web Chat</title>
<script src="https://cdn.botframework.com/botframework-webchat/latest/webchat.js"></script>
<script type="text/javascript" src="https://alcdn.msauth.net/lib/1.2.0/js/msal.js"></script>
<script src="https://unpkg.com/@azure/storage-blob@10.3.0/browser/azure-storage.blob.min.js"
  integrity="sha384-fsfhtLyVQo3L3Bh73qgQoRR328xEeXnRGdoi53kjo1uectCfAHFfavrBBN2Nkbdf"
  crossorigin="anonymous">
</script>
<script type="text/javascript">
  if (typeof Msal === 'undefined') document.write(unescape("%3Cscript src='https://alcdn.msauth.net/lib/1.2.0/js/msal.js' type='text/javascript' %3E%3C/script%3E"));
</script>

  <style>
  html,
  body {
    height: 100%;
  }

  body {
    margin: 0;
  }

  .modal {
    display: none; /* Hidden by default */
    position: fixed; /* Stay in place */
    z-index: 1; /* Sit on top */
    padding-top: 100px; /* Location of the box */
    left: 0;
    top: 0;
    width: 100%; /* Full width */
    height: 100%; /* Full height */
    overflow: auto; /* Enable scroll if needed */
    background-color: rgb(0, 0, 0); /* Fallback color */
    background-color: rgba(0, 0, 0, 0.4); /* Black w/ opacity */
  }

  .modal-content {
    background-color: #fefefe;
    margin: auto;
    padding: 10px;
    border: 1px solid #888;
    width: 500px;
    height: 575px;
  }
  .close {
    color: black;
    float: right;
    font-size: 28px;
    font-weight: bold;
  }

  .close:hover,
  .close:focus {
    color: #000;
    text-decoration: none;
    cursor: pointer;
  }

  .main {
    margin: 18px;
    border-radius: 4px;
  }

  div[role="form"] {
    background-color: #3392ff;
  }

  #webchat {
    position: center;
    height: 530px;
    width: 100%;
    top: 60px;
    overflow: hidden;
  }
  #heading {
    padding-bottom: 5px;
  }

  h1 {
    font-size: 14px;
    font-family: Segoe UI;
    font-style: normal;
    font-weight: 600;
    font-size: 14px;
    line-height: 20px;
    color: #f3f2f1;
    letter-spacing: 0.005em;
    display: table-cell;
    vertical-align: middle;
    padding: 13px 0px 0px 20px;
  }

  /*#chatwindow
{
  height: 530px;
  width: 100%;
  overflow: hidden;
top: 60px;
 position: center;
}*/

  #login {
    position: fixed;
    margin-left: 150px;
  }

  .span {
    font-weight: bold;
  }
  #myBtn {
    position: fixed;
    float: right;
    outline: none;
    width: 60px;
    height: 80px;
    margin: auto auto auto 10px;
  }
  button:hover {
    background-color: transparent;
  }
  </style>

</head>

<body>
  <button id="myBtn" type="button">Power Virtual Agent</button>
  <div id="myModal" class="modal">
  <div class="modal-content" style="background-color: #ffd933">
     <span class="close">&times;</span>
<div id="chatwindow">
  <div id="heading">
    <div><span>SSO Test Bot</span></div>
  </div>
  <!-- <div style="z-index: 100;position: absolute;margin-top: 50px;width: 100%;">
     <div>
      <label id="userName" name="userName" style="width:75%;height:15px;border-color: Transparent;">Not logged in.</label>
      <button id="login" name="login" onclick="onSignInClick()">Log In</button>
    </div>
  </div> -->
  <div id="webchat"></div>
</div>
</div>
</div>



<script>
  //Button code begins here
  // Get the modal
  var modal = document.getElementById("myModal");

  // Get the button that opens the modal
  var btn = document.getElementById("myBtn");

  // Get the <span> element that closes the modal
  var span = document.getElementsByClassName("close")[0];

  // When the user clicks the button, open the modal
  btn.onclick = function () {
    modal.style.display = "block";
  };

  // When the user clicks on <span> (x), close the modal
  span.onclick = function () {
    modal.style.display = "none";
  };

  // When the user clicks anywhere outside of the modal, close it
  window.onclick = function (event) {
    if (event.target == modal) {
      modal.style.display = "none";
    }
  };
  //Button code ends here
</script>



<script>

function onSignin(idToken)
{
  alert("KMT - Inside onSignin: " + idToken);
  let user = clientApplication.getAccount();
  alert("KMT - user.name: " + user.name);
  document.getElementById("userName").innerHTML = "Currently logged in as " + user.name;
  let requestObj1 = {
    scopes: ["user.read", 'openid', 'profile']
  };
}

function onSignInClick()
{
  //console.log("Inside onSignInClick");
  let requestObj = {
    scopes: ["user.read", 'openid', 'profile']
  };

  clientApplication.loginPopup(requestObj).then(onSignin).catch(function (error) {console.log(error) });
}

function getOAuthCardResourceUri(activity) {
  if (activity && activity.attachments && activity.attachments[0] &&
       activity.attachments[0].contentType === 'application/vnd.microsoft.card.oauth' &&
       activity.attachments[0].content.tokenExchangeResource) {
     // asking for token exchange with AAD
         return activity.attachments[0].content.tokenExchangeResource.uri;
   }
}

function exchangeTokenAsync(resourceUri) {
  let user = clientApplication.getAccount();
  if (user) {
     let requestObj = {
       scopes: ["user.read", 'openid', 'profile']
     };
  return clientApplication.acquireTokenSilent(requestObj).then(function (tokenResponse) {
    return tokenResponse.accessToken;
     })
     .catch(function (error) {
       console.log(error);
     });
     }
     else {
     return Promise.resolve(null);
   }
}

async function fetchJSON(url, options = {}) {
    const res = await fetch(url, {
      ...options,
      headers: {
           ...options.headers,
           accept: 'application/json'
      }});

      if (!res.ok)
      {
        throw new Error(`Failed to fetch JSON due to ${res.status}`);
      }

      return await res.json();
  }
</script>

<script>
     var clientApplication;
     (function ()
     {
       var msalConfig = {
         auth:{
               clientId: '7dd5c894-17f5-4fd1-be79-cb4900590418',
               authority: 'https://login.microsoftonline.com/e7ee4711-c0b1-4311-b500-b80d89e5b298'
         },
         cache:{
               cacheLocation: 'localStorage',
               storeAuthStateInCookie: true
         }};
       if (!clientApplication)
       {
            clientApplication = new Msal.UserAgentApplication(msalConfig);
       }
     } ());

(async function main() {

  // Add your BOT ID below

  var BOT_ID = "ec06d968-e213-4d13-90c7-fe78bcadbfa6";
  var theURL = "https://powerva.microsoft.com/api/botmanagement/v1/directline/directlinetoken?botId=" + BOT_ID;

    var userId = clientApplication.account?.accountIdentifier != null ?
                    ("You-customized-prefix" + clientApplication.account.accountIdentifier).substr(0, 64)
                    : (Math.random().toString() + Date.now().toString()).substr(0,64);

  const { token } = await fetchJSON(theURL);
  //console.log("Token inside main: " + JSON.parse(token));

  const directLine = window.WebChat.createDirectLine({ token });
  //console.log("directLine inside main: " + directLine);

  const store = WebChat.createStore({}, ({ dispatch }) => next => action => {const { type } = action;
  //console.log("store inside main: " + JSON.parse(store));

  if (action.type === 'DIRECT_LINE/CONNECT_FULFILLED')
  {
           dispatch({
              type: 'WEB_CHAT/SEND_EVENT',
               payload:
             {
                  name: 'startConversation',
                  type: 'event',
                  value:
                {
                    text: "hello"
                }
               }
              });
               return next(action);
   }
   if (action.type === 'DIRECT_LINE/INCOMING_ACTIVITY')
   {
         const activity = action.payload.activity;
         let resourceUri;
         if (activity.from && activity.from.role === 'bot' && (resourceUri = getOAuthCardResourceUri(activity)))
       {
            exchangeTokenAsync(resourceUri).then(function (token) {
            if (token)
          {
            //console.log("Inside if token: " + token);
                   directLine.postActivity({
                         type: 'invoke',
                         name: 'signin/tokenExchange',
                         value:
                     {
                             id: activity.attachments[0].content.tokenExchangeResource.id,
                             connectionName: activity.attachments[0].content.connectionName,
                             token
                         },
                         "from":
                     {
                             id: userId,
                             name: clientApplication.account.name,
                             role: "user"
                         }
                       }).subscribe(id => {
                            if (id === 'retry')
                        {   // bot was not able to handle the invoke, so display the oauthCard
                                return next(action);
                            }   // else: tokenexchange successful and we do not display the oauthCard
                         },
                         error => {
                                // an error occurred to display the oauthCard
                                return next(action);
                         }
          );
          return;
        }
      else return next(action);
    });
    }
  else return next(action);
  }
  else return next(action);
  });

  const styleOptions = {
     // Add styleOptions to customize Web Chat canvas
     hideUploadButton: true
  };


    window.WebChat.renderWebChat({
            directLine: directLine,
        store,
              userID:userId,
        styleOptions
          },
          document.getElementById('webchat')
    );
})().catch(err => console.error("An error occurred: " + err));
</script>
</body>
</html>

Every time I click on the Power Virtual Agent button I see the login OAuth card in the bot.

Chatbot OAuth card image

What I am looking for is I do not want the login OAuth card to be displayed. Instead I wanted to be logged in directly as I have already login to my SharePoint website.

I have created 2 App registrations in Azure AAD 1 for Authentication and the other for SSO following the document provided by Microsoft

标签: javascriptoauth-2.0azure-active-directorychatbotmsal.js

解决方案


不幸的是,SharePoint Online 无法与机器人服务集成 SharePoint SSO,因为它的限制要大得多。无法从 SharePoint 获取 AAD 令牌并将其发送到机器人以供使用。

作为一种解决方法,您只能将登录用户的 UserID 传递给机器人,以便它可以通过调用图形 API/SharePoint API 来获取访问令牌以使用资源,以再次获取新的访问令牌以访问用户/SP使用 Bot 中的应用权限的详细信息。

但是这种方法存在安全隐患,任何拥有 Bot 直连令牌的人都可以传递任何有效的电子邮件 ID,并且进一步使用 Bot 中的 AAD 应用程序权限获取该用户(提供的电子邮件 ID)详细信息的访问令牌。但最终这些用户和电子邮件 ID 只能来自您组织的租户。我不能说它是安全的/脾气暴躁的。

这只是一种解决方法,因为 SharePoint Online 无法与机器人服务集成 SharePoint SSO。如果您不希望用户使用 AAD 重新进行身份验证,则此(解决方法)是目前唯一可用的选项。


推荐阅读