Authenticate against the 1E Platform and obtain a token
This page describes the interactive authentication and code samples that can be adapted by the customer while writing their own integration software.
It should also be understood that these pages will use {apiRoot} symbol for the Root of the Portal (not Consumer) API, as it is Portal API that deals with authentication requests. The root of that API will look like https://your.tachyon.server/Tachyon/api (for 9.X and 23.X) or https://your.tachyon.server/api for versions 24.1 and later. (note the word “Tachyon” is missing in 24.1 and later versions). It is assumed that the code contained on these pages will be adapted by configuring the {apiRoot} correctly and that any other configuration (like JWT certificates and user mapping) have also been carried out.
Authentication via IdP’s web page
Interactive authentication is performed via the IdP’s web page and requires username/password and few other steps to complete the authentication. This mode of authentication is the best option for the applications having a UI that allows the user to navigate to IdP’s page and where explicit authentication is not an issue.
Authentication using a JWT
Non-interactive authentication using JWT is performed in-code and does not require any usernames or passwords. This authentication mode can be used for applications where UI cannot display IdP’s login page or where such display would be an issue. It can also be used for service accounts or to group all (or some) users of an application behind a single (or several) accounts in the 1E Platform without having a one-to-one mapping between an IdP user and a 1E Platform principal.
Implementing interactive authentication
This page describes how to perform interactive authentication against the 1E Platform and your IdP in order to obtain a token.
How it works
Authentication process requires a secret (as explained in the later steps) to be created and has the following steps:
Initial request for authentication is made to the 1E Platform that returns state and a URL that needs to be called in order to perform authentication with the IdP
Visit the URL provided in the previous step and perform the required authentication
Poll for status change to establish in the background when the token is ready, or the process is failed
Upon successful authentication, retrieve the token
Before we begin
Authentication process requires a secret called a nonce. This is usually an array of random 32 bytes that should be created before the authentication process is started.
Once you have the array, you need to create two things:
A URL-safe Base64 encoded of the nonce. Let’s call this BASEDNONCE
A URL-safe Base64 encoded SHA256 of the nonce (you need to obtain SHA256 of the nonce and then Base64 encode it). Let’s call this HASHEDNONCE
The original nonce is not used afterwards and only those two values, BASEDNONCE and HASHEDNONCE are used.
It is strongly advised that nonce is randomized each time a new request is made, as reusing a nonce decreases security of the authentication process.
Authentication request
Initial request should be made to {apiRoot}/Authentication/RequestAuthentication and this request should have a query string parameter called ecpn. You should put the HASHEDNONCE as the value of the parameter.
This means that the initial request will look something like this:
https://your.tachyon.server/api/Authentication/RequestAuthentication?ecpn=e0bebd22819993425814866b62701e2919ea26f1370499c1037b53b9d49c2c8a
and you will receive something like this from the API as a response:
{ "AuthenticationUrl": "https://login.microsoftonline.com/06c15aaf-bd3b-4eac-aac9-ef6e9ce805b0/oauth2/v2.0/authorize?response_type=code&prompt=login&client_id=9858ac43-555e-4500-9ccd-bac33e1ddfba&state=5PVOGbg0lh_Juj5P9D7dLAAhkY4N-f4KgXJ8jgH88dQ.ZDQ2ZDQyNDNjMTM3ZGZhY2YzNzNmYzBhOGFmYmYzNTNiZDJjNzU4OGVjNTQ0YTY4NTdjYjExOTljNjFhMGE1Ng.in_yI295_1yUC-nvqdHgIS_-3gOVVj9G4SANupo8JCdWoqaHb7iqYsPEJvqEKa0EzrualgWociVuL63HttqbN79bgy_ZRHXO3-EYBwn_JE6dvIckpOBkAKQghdMyCn4ctidAhnkIF0IER8pHI4aNuMVRa4_um7KILM4GwW36luVOEgP_3xpGrofgKp_dDHYFuJq6a216qok65_vGK_fzc6sES9pcau9axE-1DWZ2a_JTwXZy0K3xx38hx4oxSfb5cnFn-e1A0hP06In4dWImF6t_6Lawc06-Ya84vPUmg2SR45NwudUJamHdjsC_G_rqWIWi98aIOEqjJGJDZvOGNg&scope=openid&redirect_uri=https://your.tachyon.server:443/Portal/api/Authentication/IdentityProviderRedirect&code_challenge=h1oqLd3TmMmk82j_O72FIZWN_4vKFN9mEDXndQpLAdw&code_challenge_method=S256", "State": "5PVOGbg0lh_Juj5P9D7dLAAhkY4N-f4KgXJ8jgH88dQ.ZDQ2ZDQyNDNjMTM3ZGZhY2YzNzNmYzBhOGFmYmYzNTNiZDJjNzU4OGVjNTQ0YTY4NTdjYjExOTljNjFhMGE1Ng.in_yI295_1yUC-nvqdHgIS_-3gOVVj9G4SANupo8JCdWoqaHb7iqYsPEJvqEKa0EzrualgWociVuL63HttqbN79bgy_ZRHXO3-EYBwn_JE6dvIckpOBkAKQghdMyCn4ctidAhnkIF0IER8pHI4aNuMVRa4_um7KILM4GwW36luVOEgP_3xpGrofgKp_dDHYFuJq6a216qok65_vGK_fzc6sES9pcau9axE-1DWZ2a_JTwXZy0K3xx38hx4oxSfb5cnFn-e1A0hP06In4dWImF6t_6Lawc06-Ya84vPUmg2SR45NwudUJamHdjsC_G_rqWIWi98aIOEqjJGJDZvOGNg" }
Examining the response to initial authentication request
The response you received contains two properties.
First is the authentication URL. You should take this URL and using your web browser, navigate to display the IdP login page.
Second property is called State. This identifies your request and you will need this to check what is happening with your request and to retrieve the token.
Authenticating against the IdP
Authentication against your IdP has to be done outside the bounds of the 1E Platform, as the IdP is an independent system. You can handle it however you like, but the process must be completed successfully in order for the authentication process to complete. As the IdP will call 1E Platform when you have successfully authenticated. If that call never comes, the token will not be issued.
Checking the status of your authentication request
After the authentication process has been initiated, you should start polling to check if it has completed.
You do that by calling GET on {apiRoot}/Authentication/CheckAuthenticationState while providing a payload with the state you have received in the response to the authentication request:
{ "State" : "5PVOGbg0lh_Juj5P9D7dLAAhkY4N-f4KgXJ8jgH88dQ.ZDQ2ZDQyNDNjMTM3ZGZhY2YzNzNmYzBhOGFmYmYzNTNiZDJjNzU4OGVjNTQ0YTY4NTdjYjExOTljNjFhMGE1Ng.in_yI295_1yUC-nvqdHgIS_-3gOVVj9G4SANupo8JCdWoqaHb7iqYsPEJvqEKa0EzrualgWociVuL63HttqbN79bgy_ZRHXO3-EYBwn_JE6dvIckpOBkAKQghdMyCn4ctidAhnkIF0IER8pHI4aNuMVRa4_um7KILM4GwW36luVOEgP_3xpGrofgKp_dDHYFuJq6a216qok65_vGK_fzc6sES9pcau9axE-1DWZ2a_JTwXZy0K3xx38hx4oxSfb5cnFn-e1A0hP06In4dWImF6t_6Lawc06-Ya84vPUmg2SR45NwudUJamHdjsC_G_rqWIWi98aIOEqjJGJDZvOGNg" }
Warning
Do not provide any other data with these requests. Specifically, do not provide BASEDNONCE as seen in the next paragraph. You can only attempt token retrieval once. If you don't do it correctly you will not be able to retrieve the token again and you will have to start the authentication process again.
The API will respond with following data:
{ "Status":"AuthenticationRequested", "Data":"" }
The status can be one of following values:
AuthenticationRequested - means the authentication is still in progress but it hasn’t finished yet. Token isn’t ready and you should keep pooling
AuthenticationResultNotAvailable - this means this authentication attempt has failed and you will not be able to retrieve the token. You have to start a new authentication process. The system does not return a specific reason on purpose as a security measure
AuthenticationSuccessful - means the authentication has been successful and the token can be retrieved
Retrieving the token
Token is retrieved via the same endpoint you used to check the authentication state, but you have to also provide BASEDNONCE:
{ "State" : "5PVOGbg0lh_Juj5P9D7dLAAhkY4N-f4KgXJ8jgH88dQ.ZDQ2ZDQyNDNjMTM3ZGZhY2YzNzNmYzBhOGFmYmYzNTNiZDJjNzU4OGVjNTQ0YTY4NTdjYjExOTljNjFhMGE1Ng.in_yI295_1yUC-nvqdHgIS_-3gOVVj9G4SANupo8JCdWoqaHb7iqYsPEJvqEKa0EzrualgWociVuL63HttqbN79bgy_ZRHXO3-EYBwn_JE6dvIckpOBkAKQghdMyCn4ctidAhnkIF0IER8pHI4aNuMVRa4_um7KILM4GwW36luVOEgP_3xpGrofgKp_dDHYFuJq6a216qok65_vGK_fzc6sES9pcau9axE-1DWZ2a_JTwXZy0K3xx38hx4oxSfb5cnFn-e1A0hP06In4dWImF6t_6Lawc06-Ya84vPUmg2SR45NwudUJamHdjsC_G_rqWIWi98aIOEqjJGJDZvOGNg" "Nonce": "your BASEDNONCE goes here" }
If the data you have provided is correct you will receive the token:
{ "Status":"AuthenticationSuccessful", "Data":"your token will be here" }
Warning
You have only one attempt to retrieve the token. This means that for given state (for given request) the system will allow you to make precisely ONE call to {apiRoot}/Authentication/CheckAuthenticationState where your provide both State and Nonce. If you provide wrong data or make the call too early, you will not be able to retrieve the token and you will have to start a new authentication request.
What if something goes wrong?
Every API call response should be examined for errors. In most cases you will receive standard 1E Platform errors (see Handling errors returned by Tachyon for more details), but there might be situations where something else is returned.
Consumer SDK support
Currently Consumer SDK does not support authentication and you have to write you own code. We’re providing examples to help you with that.
Implementing interactive authentication - C# example
This page contains a code example written in C# that demonstrates interactive authentication. This code sample should be adapted by the customer when writing their own integration software.
Note
Any code given is without any warranty and is meant only for demonstration and is not production ready code. This means that the code has brevity omitted things like error and exception handling.
using System; using System.Diagnostics; using System.Runtime.InteropServices; using System.Security.Cryptography; using System.Text; using System.Text.Json; namespace InteractiveAuthenticationClient { public class Program { private const string ApiRoot = "https://your.tachyon.server.address/api/"; private const string RequestAuthenticationEndpoint = "Authentication/RequestAuthentication?ecpn={0}"; private const string CheckAuthenticationStateEndpoint = "Authentication/CheckAuthenticationState"; static void Main(string[] args) { Console.WriteLine("Creating authentication nonce..."); // Prepare nonce var nonce = CreateNonce(); // original nonce, never supplied to any api var basedNonce = ToUrlSafeBase64(nonce); // base64 encoded nonce, used when retrieving a token var hashedNonce = CreateSha256(nonce); // hash of the original nonce, never supplied to any api var basedHashedNonce = ToUrlSafeBase64(hashedNonce); // base64 encoded has of the nonce, to be supplied in the original authentication request using (var client = new HttpClient()) { var endpoint = ApiRoot + string.Format(RequestAuthenticationEndpoint, basedHashedNonce); Console.WriteLine($"Calling {endpoint} to initiate authentication"); AuthRequestResponse obj = Get<AuthRequestResponse>(client, endpoint); if (obj != null) { if (!string.IsNullOrEmpty(obj.AuthenticationUrl)) { Console.WriteLine($"Received authentication URL {obj.AuthenticationUrl} and state {obj.State}. Opening web browser for the user to complete authentication process."); OpenBrowser(obj.AuthenticationUrl); string token = LoopForToken(obj.State, basedNonce); if (token != null) { Console.WriteLine($"Token retrieved: {token}"); } else { Console.WriteLine("Token could not be retrieved. Please try again."); } } } } } private static string LoopForToken(string objState, string basedNonce) { var continueLoop = true; string retVal = null; var endpoint = ApiRoot + CheckAuthenticationStateEndpoint; using (var client = new HttpClient()) { while (continueLoop) { var payload = new AuthenticationCheckRequestPayload { State = objState }; var result = Post<AuthenticationEvent, AuthenticationCheckRequestPayload>(client, payload, endpoint); if (result != null) { switch (result.Status) { case "AuthenticationResultNotAvailable": { // There's an issue with the request. Either the state is incorrect or you tried to retrieve the token incorrectly or it already has been redeemed. // You will need to initiate new authentication request as you cannot continue with this one. Console.WriteLine("Something went wrong during the authentication process. Please start a new authentication process."); continueLoop = false; break; } case "AuthenticationRequested": { Console.WriteLine("Authentication process is still in progress. Will try again in a few seconds."); Thread.Sleep(2000); // Authentication request is still being processed. break; } case "AuthenticationSuccessful": { Console.WriteLine("Authentication process has finished. Retrieving token..."); // Authentication has been successful and token can be retrieved. var payload2 = new AuthenticationCheckRequestPayload { State = objState, Nonce = basedNonce }; var result2 = Post<AuthenticationEvent, AuthenticationCheckRequestPayload>(client, payload2, endpoint); if (result2 != null) { retVal = result2.Data; } else { Console.WriteLine("Failed to retrieve token."); } continueLoop = false; break; } } } else { Console.WriteLine("Call to retrieve authentication request has failed."); continueLoop = false; } } } return retVal; } private static byte[] CreateSha256(byte[] buffer) { using (var sha256 = new SHA256CryptoServiceProvider()) { return sha256.ComputeHash(buffer); } } private static byte[] CreateNonce() { byte[] nonce = new byte[32]; Random rand = new Random(); rand.NextBytes(nonce); return nonce; } private static string ToUrlSafeBase64(byte[] arg) { var Base64PadCharacter = '='; var Base64Character62 = '+'; var Base64Character63 = '/'; var Base64UrlCharacter62 = '-'; var Base64UrlCharacter63 = '_'; var s = Convert.ToBase64String(arg); s = s.Split(Base64PadCharacter)[0]; // RemoveAccount any trailing padding s = s.Replace(Base64Character62, Base64UrlCharacter62); // 62nd char of encoding s = s.Replace(Base64Character63, Base64UrlCharacter63); // 63rd char of encoding return s; } public static void OpenBrowser(string url) { try { Process.Start(url); } catch { // hack because of this: https://github.com/dotnet/corefx/issues/10361 if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { url = url.Replace("&", "^&"); Process.Start(new ProcessStartInfo("cmd", $"/c start {url}") { CreateNoWindow = true }); } else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) { Process.Start("xdg-open", url); } else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) { Process.Start("open", url); } else { throw; } } } private static T Get<T>(HttpClient client, string url) { var result = client.GetAsync(url).Result; if (result.IsSuccessStatusCode) { var data = result.Content.ReadAsStringAsync().Result; if (!string.IsNullOrEmpty(data)) { T obj = JsonSerializer.Deserialize<T>(data); if (obj != null) { return obj; } } } return default; } private static TOut Post<TOut, TIn>(HttpClient client, TIn payload, string url) { var stream = new MemoryStream(); JsonSerializer.Serialize(stream, payload); stream.Seek(0, SeekOrigin.Begin); var reader = new StreamReader(stream); var serializedPayload = reader.ReadToEnd(); reader.Close(); stream.Close(); var content = new StringContent(serializedPayload, Encoding.UTF8, "application/json"); var result = client.PostAsync(url, content).Result; if (result.IsSuccessStatusCode) { var data = result.Content.ReadAsStringAsync().Result; if (!string.IsNullOrEmpty(data)) { TOut obj = JsonSerializer.Deserialize<TOut>(data); if (obj != null) { return obj; } } } return default; } private static string Post<TIn>(HttpClient client, TIn payload, string url) { var stream = new MemoryStream(); JsonSerializer.Serialize(stream, payload); stream.Seek(0, SeekOrigin.Begin); var reader = new StreamReader(stream); var serializedPayload = reader.ReadToEnd(); reader.Close(); stream.Close(); var content = new StringContent(serializedPayload, Encoding.UTF8, "application/json"); var result = client.PostAsync(url, content).Result; if (result.IsSuccessStatusCode) { return result.Content.ReadAsStringAsync().Result; } return default; } private class AuthRequestResponse { public string AuthenticationUrl { get; set; } public string State { get; set; } } private class AuthenticationCheckRequestPayload { public string State { get; set; } public string? Nonce { get; set; } } private class AuthenticationEvent { public string Status { get; set; } public string Data { get; set; } } } }