Hi,
I am trying to connect SSO in PVA on the Sharepoint website. I see the below error on the chrome console.
I see a syntax error from the below line of code. I am following the SSO configuration by the doc provided below: "https://docs.microsoft.com/en-us/power-virtual-agents/configure-sso"
The below code is from the doc.
var userID = clientApplication.account?.accountIdentifier != null ? ("Your-customized-prefix-max-20-characters" + clientApplication.account.accountIdentifier).substr(0,64) : (Math.random().toString() + Date.now().toString().substr(0,64)
I had posted by query before as well but no luck.. https://powerusers.microsoft.com/t5/General/How-to-configure-Single-Sign-On-in-PVA-with-Sharepoint-and-Teams/m-p/633564#M1006
Can anyone please help me out here?
Regards,
Hemanth
Hi,
I was wondering if you have figured out how to fix the 403 issue. I am having the same problem.
thanks
Wrong post apologies
Thank you @BoLi...appreciate it.
There are also several errors within the scripts provided within that article. I used this thread and one other to solve several of those.
I was wondering if you can help with the 403 error that I reported above somehow.
I think it just needs someone who has done this before to possibly advise as I'm confident that I have followed the article closely.
I'd be more than happy to provide my findings and contribute to improving the article by providing the correct working script etc.
Is there any chance we can connect virtually over a call?
Cheers.
Thanks, the document is indeed very confused and not clear. I will ask someone to update the doc. Thank you for pointing it out
Hi,
This thread has helped me progress through various errors so thanks everyone for that.
I believe I'm real close but am getting a 403 error when the direct line API is called.
I have followed the PVA SSO guide and gone through all the steps - Configure single sign-on - Power Virtual Agents | Microsoft Docs.
Also, I'm somewhat confused by contradicting instructions.
Please note that the two steps (in italics from the SSO article) below seem to contradict each other as to which app the expose an API scope need to be added to. Step 1 says add it to the initial app reg. However, in the following section step 4, it refers to the canvas app.
Which one should the scope be configured in? - Initial app reg or the canvas app?
Define a custom scope for your bot
Open the app registration that you created when you configured authentication.
Step 4 refers to the Canvas app reg - Enter the full scope URI from the Expose an API blade for the canvas app registration in the Token exchange URL field. The URI will be in the format of api://1234-4567/scope.name.
Can someone please help? @BoLi @PaulCullivan @Anonymous
Thanks in advance.
Thank you for responding back. I have done the above code changes, I am passing the user.userName as a loginHint. Please find the below code.
function exchangeTokenAsync(resourceUri)
{
let user = clientApplication.getAccount();
console.log("user.userName is: " + user.userName); // User Email ID
document.getElementById("userName").innerHTML = "Welcome " + user.name; // User Name
let requestObj = {
scopes: [resourceUri, "openid", "profile"],
loginHint: user.userName,
};
return clientApplication.acquireTokenSilent(requestObj).then(function (tokenResponse) {
return tokenResponse.accessToken;
}).catch(function (error)
{
console.log("Error from exchangeTokenAsync function" + error);
});
}
Below is the MSAL code.
var clientApplication;
(function () {
var msalConfig = {
auth:
{
clientId:'<Canvas(SSO) App Client ID> ',
authority:'https://login.microsoftonline.com//<Directory ID>',
redirectUri:'<My SharePoint website url were the bot is deployed>'
},
cache:
{
cacheLocation: "localStorage",
storeAuthStateInCookie: false,
},
};
if (!clientApplication)
{
clientApplication = new Msal.UserAgentApplication(msalConfig);
}
})();
Everytime my code enters into the if(id === "retry") block of code.
Errors from the logs:
id: retry - bot was not able to handle the invoke, so display the oauthCard
Error from exchangeTokenAsync functionClientAuthError: Token calls are blocked in hidden iframes
Failed to load resource: the server responded with a status of 502 ()
But I see my userName being fetched by the MSAL.
Below is the exchangeTokenAsync function
I am not sure why this always fails and displays the login card.
Hey HermanthN, per our conversation here's what we did with the relevant changes. Apologies for the delay.
@using Microsoft.AspNetCore.Http
@using Microsoft.Extensions.Configuration
@inject IHttpContextAccessor HttpContextAssessor
@inject IConfiguration Configuration
@{
string userId = HttpContextAssessor.HttpContext.User.Claims.FirstOrDefault(user => user.Type == "preferred_username").Value;
string redirectUri = Configuration.GetValue<string>("ConnectionStrings:redirectUri");
string clientId = Configuration.GetValue<string>("AzureAd:ClientId");
string botId = Configuration.GetValue<string>("ConnectionStrings:BotId");
string authority = $"{Configuration.GetValue<string>("AzureAd:Instance")}{Configuration.GetValue<string>("AzureAd:TenantId")}";
}
This gets you the user name through httpcontextassessor that I mentioned earlier. From here you can pass this as a login hint:
function exchangeTokenAsync(resourceUri) {
let requestObj = {
scopes: [resourceUri, 'openid', 'profile'],
loginHint: '@userId'
};
return clientApplication.acquireTokenSilent(requestObj)
.then(function (tokenResponse) {
return tokenResponse.accessToken;
})
.catch(function (error) {
console.log(error);
});
}
Note the redirect URI, that's also relevant and passed in here:
var clientApplication;
(function () {
var msalConfig = {
auth: {
// Client/tenant ID from CosineADOSupport app registration
clientId: '@clientId',
authority: '@authority',
redirectUri: '@redirectUri'
},
cache: {
cacheLocation: 'localStorage',
storeAuthStateInCookie: false
}
};
if (!clientApplication) {
clientApplication = new Msal.UserAgentApplication(msalConfig);
}
}());
That's pretty much the difference between our code. Let me know if you have any other questions.
you can refer the sample code on our github to get an insight of how to pass token to PVA. you can find the github file link in SSO configuration page
Hi @BoLi
Thank you for your response. Can you please explain me how to pass the login token to PVA in order for SSO to work by providing code samples? I really am not able to figure this out.
I have followed the doc to provide the API Permissions for both of the canvas and authentication app.
Below is the snapshot of the App registration for the Canvas App: I have added 2 extra roles here.
Below is the snapshot of the App registration for the Authentication App:
The Application id looks weird because I have edited it due to security reasons.
Below is the current code that I am using.
<!DOCTYPE html>
<html>
<head>
<title>Virtual Agent</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.msftauth.net/lib/1.2.0/js/msal.js' type='text/javascript' %3E%3C/script%3E"));
</script>
<script>
var clientApplication;
(function () {
var msalConfig = {
auth: {
clientId: '<CLIENT ID I HAVE REMOVED>',
authority: 'https://login.microsoftonline.com/<DIRECTORY ID I HAVE REMOVED>'
},
cache: {
cacheLocation: 'localStorage',
storeAuthStateInCookie: false
}
};
if (!clientApplication) {
clientApplication = new Msal.UserAgentApplication(msalConfig);
}
} ());
</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;
}
#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">
<!-- Modal content -->
<div class="modal-content" style="background-color: #ffd933">
<span class="close">×</span>
<div id="chatwindow">
<div id="heading">
<img src="https://www.flaticon.com/svg/vstatic/svg/4061/4061262.svg?token=exp=1611082398~hmac=d7fe65d90930596808248cc855fd1fda" width="42" height="30" alt="KMT-logo"/>
<span class="span"><strong>Virtual Agent</strong></span>
</div>
<div id="webchat"></div>
</div>
</div>
</div>
<!--Button code begins here-->
<script>
// 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";
}
};
</script>
<!--Button code ends here-->
<script>
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: [resourceUri]
};
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) {
console.log(`KMT - Failed to fetch JSON due to ${res.status}`);
throw new Error(`Failed to fetch JSON due to ${res.status}`);
}
return await res.json();
}
</script>
<script>
(async function main() {
// Add your BOT ID below
var BOT_ID = "<BOT ID I HAVE REMOVED>";
var theURL = "https://powerva.microsoft.com/api/botmanagement/v1/directline/directlinetoken?botId=" + BOT_ID;
const {
token
} = await fetchJSON(theURL);
const directLine = window.WebChat.createDirectLine({
token
});
var userID = clientApplication.account?.accountIdentifier != null ?
("Your-customized-prefix-max-20-characters" + clientApplication.account.accountIdentifier).substr(0, 64) :
(Math.random().toString() + Date.now().toString()).substr(0, 64); // Make sure this will not exceed 64 characters
const store = WebChat.createStore({}, ({
dispatch
}) => next => action => {
const {
type
} = action;
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) {
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 => {
console.log("KMT - id: " + 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
botAvatarInitials: "BT",
userAvatarInitials: "UR",
//accent: '#00809d',
botAvatarBackgroundColor: "#FFFFFF",
userAvatarBackgroundColor: "#FFFFFF",
botAvatarImage:
"https://www.flaticon.com/svg/vstatic/svg/1587/1587565.svg?token=exp=1611082485~hmac=740caa18cae9c7b8ba42daccc841eef0",
userAvatarImage:
"https://www.flaticon.com/svg/vstatic/svg/64/64572.svg?token=exp=1611082510~hmac=c15408c5ec67720b3be4b75976161466",
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>
Thanks in advance!
@Anonymous , you will still have to pass the login token to PVA in order for SSO to work. that is a must. For the error you attached, it says the user has not consent to use the application which means you don't grant tenant level consent for your app(Canvas PVA bot SSO). if the app is for PVA bot, then you must grant tenant level consent first(this is already mentioned in . Besides, the application id looks weird to me, I expect it to be a AAD application id which is a GUID but this is different. Make sure that you are using AAD V2 application.
Pablo Roldan
25
Romain The Low-Code...
23
stampcoin
10