Validate a Token through a URL
An API token validation call is a security check used to confirm a token's authenticity and ensure it has the proper permissions. Extensibility tokens can be validated through a call to the Public Access site. This token validation feature is only available for HTML and Div widgets. This provides another way for customers and business partners to validate tokens from custom widgets in Public Access.
|
|
IMPORTANT: The way the tokens are validated has changed slightly, and code validated prior to Trimble Unity Permit Public Access 9.0 must be updated to ensure continued functionality. Please check the provided script examples for more details.
You can validate an extensibility token by making a direct call to the Public Access site. This provides an easy alternative to writing your own token validation code.
- The URL to validate a token is https://myServer/myPAsite/services/extensibility/validatesignaturetoken?token={token}. Replace {token} with the token from your custom widget.
The following is an example of how you can validate the token in your system if you used the local validation method since it has changed slightly.
function render(context: ScriptContext.RenderContext): ScriptContext.RenderConfig | false {
const element = createButton(context);
return { element };
}
function createButton(context: ScriptContext.RenderContext): HTMLElement {
const buttonElement = document.createElement('button');
buttonElement.textContent = "Go to my site";
buttonElement.onclick = () => {
// TrimbleUnityApi.Global.getExtensibilityToken() returns a Promise that resolves a string
(TrimbleUnityApi.Global.getExtensibilityToken() as Promise<string>).then(token => {
// Widgets are run in an iframe that is sandboxed for security, so calling window.open will cause the new window to inherit the sandbox limitations
// Calling TrimbleUnityApi.Global.openWindow allows the new page to escape the sanbox limitations
TrimbleUnityApi.Global.openWindow("http://localhost:5009?token=" + encodeURIComponent(token));
});
};
const retVal = document.createElement('div');
retVal.appendChild(buttonElement);
return retVal;
}
The following is an example of how you can validate a token either by calling the new URL or in your own code.
using Microsoft.AspNetCore.Mvc.RazorPages;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
namespace WebApplication1.Pages
{
public class IndexModel : PageModel
{
public string Message { get; set; }
private HttpClient _client;
// SignatureKey must match key configured for the PA site that generated the token if validation is done locally
private const string SignatureKey = "myExtensibilitySignatureKey";
public IndexModel(IHttpClientFactory httpClientFactory)
{
_client = httpClientFactory.CreateClient();
}
public async Task OnGet(string token, bool useService)
{
if (!string.IsNullOrEmpty(token))
{
if (useService)
{
var result = await _client.GetAsync($"http://myServer/myPaSite/services/extensibility/validatesignaturetoken?token={token}");
if (result.IsSuccessStatusCode)
{
var responseString = await result.Content.ReadAsStringAsync();
if (responseString.Equals("true", StringComparison.OrdinalIgnoreCase))
{
// If token is valid, decode it here to get user info
// The token is Base64 encoded JSON
var tokenAsBytes = Convert.FromBase64String(token);
var tokenString = Encoding.UTF8.GetString(tokenAsBytes);
var tokenObj = JsonSerializer.Deserialize<ExtensibilityToken>(tokenString);
Message = "Token is valid for user !" + tokenObj?.UserEmail;
}
else
{
Message = "Token is not valid!";
}
}
else
{
Message = "Service call failed!";
}
}
else
{
//The token is Base64 encoded JSON
var tokenAsBytes = Convert.FromBase64String(token);
var tokenString = Encoding.UTF8.GetString(tokenAsBytes);
var tokenObj = JsonSerializer.Deserialize<ExtensibilityToken>(tokenString);
// Generate hash from app name and date
var generatedHmacBytes = HashHMAC(SignatureKey, tokenObj.AppName + tokenObj.DateString + tokenObj.UserEmail);
string generatedHashBase64 = Convert.ToBase64String(generatedHmacBytes);
// If generated and sent hashes do no match then it isn't valid
if (generatedHashBase64 != tokenObj.Signature)
{
Message = "Signature is invalid!";
return;
}
// Check if token is expired
// DateTime.Parse will create a local date time from the UTC string
if (IsValidTokenDate(DateTime.Parse(tokenObj.DateString)))
{
Message = "Token is valid!";
}
else
{
Message = "Token is expired!";
}
}
}
else
{
Message = "Token is missing!";
}
}
private byte[] HashHMAC(string strKey, string strMessage)
{
var key = Encoding.UTF8.GetBytes(strKey);
var message = Encoding.UTF8.GetBytes(strMessage);
var hash = new HMACSHA256(key);
return hash.ComputeHash(message);
}
private bool IsValidTokenDate(DateTime? tokenDate)
{
if (tokenDate == null)
{
return false;
}
var isValid = false;
var timeSpan = DateTime.Now - tokenDate.Value;
// Time span can be whatever length you determine is best for your app
if (Math.Abs(timeSpan.TotalSeconds) <= 60)
{
isValid = true;
}
return isValid;
}
}
public class ExtensibilityToken
{
public string AppName { get; set; }
public string DateString { get; set; }
public string UserEmail { get; set; }
public string Signature { get; set; }
}
}