we run a bot using bot framework and connect to it from our website using direct line in JS. We get a token from a custom API endpoint and we store the token in sessionStorage. Then we connect to the bot using
directLine = await window.WebChat.createDirectLine({
token,
conversationId,
watermark: "0"
});
Everything is working fine, but when I leave the page open for too long the token in the sessionStorage expires. A page refresh or navigating to a different page causes a 403 error inside the createDirectLine
method. Resulting in a chat bot that can't connect for as long as the sessionStorage holds that token. This behavior is not a surprise to me, but I don't know how to handle this.
What I want is to simply clear the sessionStorge, request a new token and start a new conversation when this happens. But I don't know how to do that. How do I get the 403 error from the createDirectLine
method? Or is there a way to validate the token upfront?
I already tried putting a try/catch block around the createDirectLine
method, but the 403 error did not show up in the catch.
Thanks in advance!
I have found the solution. We can check that a token is valid by refreshing it. If refreshing causes an error, the token is not valid anymore. If refreshing succeeds, the toke will be valid for another hour.
So we added (resusable) a function to our back end to refresh the token using https://directline.botframework.com/v3/directline/tokens/refresh
.
And we changed the front end code to call our new refresh function.
Front end code:
// Gets a new token from the cloud.
async function requestToken() {
if (!sessionStorage['webchatToken']) {
const res = await fetch('https://' + serviceName + '.azurewebsites.net/api/token');
// If the request was succesfull, store the token and userId.
if (res.status == 200) {
const jsonResult = await res.json();
sessionStorage['webchatToken'] = jsonResult.token;
sessionStorage['webchatUserId'] = jsonResult.userId;
console.log(`Got token from cloud`);
// refresh the token every 15 minutes.
setTimeout(() => {
refreshToken();
}, 60000 * 15); // 15 minutes
}
// If the request was not succesfull, retry.
else {
console.log(`Tried to get token, but goterror ` + res.status + `. Retrying.`);
await requestToken();
}
}
// If there is already a token in storage, refresh the existing one instead of requesting a new one.
else {
console.log(`Got token from sessionStorage`);
await refreshToken();
}
}
// Refreshes an existing token so it doesn't expire.
async function refreshToken() {
// Refresh the token if it exists in storage.
if (sessionStorage['webchatToken']) {
const res = await fetch('https://' + serviceName + '.azurewebsites.net/api/token/refresh?token=' + sessionStorage['webchatToken'],
{
method: 'POST'
});
// If refresh was succesfull we are done.
if (res.status == 200) {
console.log(`Refreshed token`);
}
// If refresh was not succesfull, clear the token from storage and request a new one. The token is probably expired.
else {
console.log(`Tried to refresh token, but got error ` + res.status + `. Requesting new token.`);
sessionStorage.clear();
await requestToken();
}
}
// If there is no token in storage, request a new token.
else {
console.log(`Tried to refresh token, but token is not defined. Requesting new token.`);
sessionStorage.clear();
await requestToken();
}
}
Back end code:
[HttpGet]
[Route("api/token")]
public async Task<ObjectResult> GetToken()
{
HttpClient client = new HttpClient();
HttpRequestMessage request = new HttpRequestMessage(
HttpMethod.Post,
$"https://directline.botframework.com/v3/directline/tokens/generate");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", _configuration.DirectLineKey);
var userId = $"dl_{Guid.NewGuid()}";
request.Content = new StringContent(
JsonConvert.SerializeObject(new { User = new { Id = userId } }),
Encoding.UTF8,
"application/json");
var response = await client.SendAsync(request);
string token = String.Empty;
int expiresIn = 0;
if (response.IsSuccessStatusCode)
{
var body = await response.Content.ReadAsStringAsync();
token = JsonConvert.DeserializeObject<DirectLineToken>(body).token;
expiresIn = JsonConvert.DeserializeObject<DirectLineToken>(body).expires_in;
}
return Ok(new { token, userId, expiresIn });
}
[HttpPost]
[Route("api/token/refresh/")]
public async Task<ObjectResult> RefreshToken(string token)
{
HttpClient client = new HttpClient();
HttpRequestMessage request = new HttpRequestMessage(
HttpMethod.Post,
$"https://directline.botframework.com/v3/directline/tokens/refresh");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
var response = await client.SendAsync(request);
token = String.Empty;
int expiresIn = 0;
if (response.IsSuccessStatusCode)
{
var body = await response.Content.ReadAsStringAsync();
token = JsonConvert.DeserializeObject<DirectLineToken>(body).token;
expiresIn = JsonConvert.DeserializeObject<DirectLineToken>(body).expires_in;
}
if (string.IsNullOrEmpty(token))
return Problem("Token incorrect");
return Ok(new { token, expiresIn });
}
I hope posting this may be useful to somebody.