user12666201
user12666201

Reputation:

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/[email protected]/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

Upvotes: 0

Views: 339

Answers (1)

Abhiman Tiwari
Abhiman Tiwari

Reputation: 282

Unfortunately, SharePoint SSO integration with bot service is not possible with SharePoint Online, which is much more restrictive. There is no way to get the AAD token from SharePoint and send to the bot for use.

As a workaround, you can only pass the UserID of logged in user to the bot, so that it can fetch the access token to use resources by invoking the graph API/SharePoint API, to again fetch the new access token to access user/SP details using App permissions in the Bot.

But there is security catch with this approach, anyone who has the Bot directline token, can pass any valid email id, and further it will fetch access token for that user’s (supplied email id) details using AAD app permission in the Bot. But ultimately those users and email id can only be from your organization's tenant. I can't say that it's secure/ temper proof.

This is just a workaround as SharePoint SSO integration with bot service is not possible with SharePoint Online. If you don’t want users to re-authenticate with AAD, this (workaround) is the only available option at the moment.

Upvotes: 1

Related Questions