Authenticating with Exacta Maestro™
This guide shows how to both retrieve an authentication token from Exacta Maestro™, and also use it to send authorized requests to our GraphQL APIs.
Endpoints
Exacta Maestro™'s authentication services are exposed over HTTPS through Bastian Solutions's load balancer. The address varies per customer, but the service is consistently accessed with these endpoints:
- https://{load-balancer-address}/{clientId}/{environmentId}/identity/api/account/login
- https://{load-balancer-address}/{clientId}/{environmentId}/identity/api/account/register
- https://{load-balancer-address}/{clientId}/{environmentId}/identity/api/account/resetpassword
The values for load-balancer-address, clientId, and environmentId will be distributed during system setup. Note that clientId, environmentId, and identity are case-sensitive.
The login endpoint is used routinely to retrieve authentication tokens. The register and resetpassword endpoints are used for new user setup.
JSON Web Tokens
Exacta Maestro™ authenticates requests using JSON Web Tokens (JWTs). These tokens allow us to grant temporary, fine-grained access to our services. We cryptographically sign these tokens using private X.509 certificates. This process makes the JWTs tamper-resistant, meaning an attacker can neither forge a custom JWT, nor alter a JWT from the Bastian.Identity service to grant themselves higher access to Exacta Maestro™.
Credentials
During system setup, a Bastian Solutions team member will provide you with a username and password for authenticating with Exacta Maestro™ and provisioning new users.
Authentication Flow
The above diagram illustrates the process of retrieving a token from Exacta Maestro™, and using the token to send send an authenticated request to one of our secured services.
Login : The client sends a sign-in request to Exacta Maestro™'s Bastian.Identity service. The service validates the user's credentials. On passing validation, Bastian.Identity returns a JSON object containing a base64-encoded JWT token to the client.
Send Request: The client sends a GraphQL request to one of Exacta Maestro™'s services. To authenticate the HTTP request, the client must add a header:
Authorization: Bearer $JWT
Authorization is the name of the header, and the value of the header is Bearer $JWT, where $JWT is the base64-encoded token from step 1. The target service validates the token. On passing validation, the target service processes the GraphQL request and returns any response data.
Authentication Example Using C#
This example shows how to authenticate with Exacta Maestro™, and send an authenticated request to our Task Assignment service. In this example, the password is shown in plain text for simplicity. In production, please store your passwords securely.
using Newtonsoft.Json;
using System;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
namespace auth_example
{
internal class Program
{
private static readonly HttpClient httpClient = new HttpClient();
private static async Task Main(string[] args)
{
var signInCommand = new
{
UserName = "maestrouser",
Password = "a-secret"
};
var signInResponse = await PostHttpMessage<SignInResponse>(new Uri("https://maestrohost.com/clientId/environmentId/identity/api/account/login"),
signInCommand);
var graphQLRequest = new
{
Query = @"mutation createTaskGroup { taskAssignmentMutation { createTaskGroup(createTaskGroupInput: { name: ""andrew-was-here"" }) { name } } }",
OperationName = "createTaskGroup"
};
var graphQLResponse = await PostHttpMessage<object>(new Uri("https://maestrohost.com/clientId/environmentId/taskAssignment/graphql"),
graphQLRequest,
signInResponse.Jwt);
Console.WriteLine($"Exacta Maestro responded with {graphQLResponse}");
}
private static async Task<TResponse> PostHttpMessage<TResponse>(Uri uri,
object message,
string? jwtToken = null)
{
var serializedMessage = JsonConvert.SerializeObject(message);
using var content = new StringContent(serializedMessage);
content.Headers.ContentType = MediaTypeHeaderValue.Parse("application/json; charset=utf-8");
using var httpMessage = new HttpRequestMessage(HttpMethod.Post,
uri);
httpMessage.Content = content;
if (jwtToken != null)
{
httpMessage.Headers.Add("Authorization",
$"Bearer {jwtToken}");
}
using var response = await httpClient.SendAsync(httpMessage);
if (response.StatusCode != HttpStatusCode.OK)
{
throw new Exception($"Request failed with status {response.StatusCode}");
}
var responseContent = await response.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<TResponse>(responseContent)!;
}
}
public class SignInResponse
{
public string Jwt
{
get;
set;
}
}
}
Refresh JWT Tokens
JWT tokens eventually expire, and the client will need to request a new one to continue using our services. We suggest either of two strategies to handle this.
Maintain a background task which periodically calls the
/identity/api/account/login
endpoint to get a new token. Share the updated token with code that calls other Exacta Maestro™ services.Call the
/identity/api/account/login
endpoint at application startup. Use the token until you receive an HTTP 401 Unauthorized response from an Exacta Maestro™ service. Call the/identity/api/account/login
endpoint again to get a new token, then retry the faulty operation with the new token.
Provision New Users
A system admin can create new users in Exacta Maestro™. The following example shows how to do this in C#. Passwords are shown in plain text for simplicity. For production use, please store passwords securely.
Note that all new users require a password reset before their account can be used. In the below example, the password reset occurs right after registering the user to make the account immediately available. Password reset can, however, be delegated to the end user to keep the user's password private.
using Newtonsoft.Json;
using System;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text.Json;
using System.Threading.Tasks;
namespace new_user_example
{
internal class Program
{
private static readonly HttpClient httpClient = new HttpClient();
private static async Task Main(string[] args)
{
var signInCommand = new
{
UserName = "maestroadmin",
Password = "a-secret"
};
var signInResponse = await PostHttpMessage<SignInResponse>(new Uri("https://maestrohost.com/clientId/environmentId/identity/api/account/login"),
signInCommand);
var newUserRequest = new
{
UserName = "new-user",
Email = "new-user@bastiansolutions.com",
Password = "a-new-user-secret"
};
var newUserResponse = await PostHttpMessage<object>(new Uri("https://maestrohost.com/clientId/environmentId/identity/api/account/register"),
newUserRequest,
signInResponse.Jwt);
Console.WriteLine($"Exacta Maestro responded to new user request with {newUserResponse}");
var resetPasswordRequest = new
{
UserName = "new-user",
Password = "a-new-user-secret",
NewPassword = "another-new-user-secret"
};
var resetPasswordResponse = await PostHttpMessage<object>(new Uri("https://maestrohost.com/clientId/environmentId/identity/api/account/resetpassword"),
resetPasswordRequest);
Console.WriteLine($"Exacta Maestro responded to reset password request with {resetPasswordResponse}");
}
private static async Task<TResponse> PostHttpMessage<TResponse>(Uri uri,
object message,
string? jwtToken = null)
where TResponse : new()
{
var serializedMessage = JsonConvert.SerializeObject(message);
using var content = new StringContent(serializedMessage);
content.Headers.ContentType = MediaTypeHeaderValue.Parse("application/json; charset=utf-8");
using var httpMessage = new HttpRequestMessage(HttpMethod.Post,
uri);
httpMessage.Content = content;
if (jwtToken != null)
{
httpMessage.Headers.Add("Authorization",
$"Bearer {jwtToken}");
}
using var response = await httpClient.SendAsync(httpMessage);
var responseContent = await response.Content.ReadAsStringAsync();
if (response.StatusCode != HttpStatusCode.OK)
{
throw new Exception($"Request failed with status {response.StatusCode}: {responseContent}");
}
if (string.IsNullOrWhiteSpace(responseContent))
{
return new TResponse();
}
return JsonConvert.DeserializeObject<TResponse>(responseContent)!;
}
}
public class SignInResponse
{
public string Jwt
{
get;
set;
}
}
}