Appendix: Configure Web API for Asymmetric Authentication
Government customers can use OneStream IdentityServer (OIS) for authentication with Private Key JSON Web Token (JWT) assertion to call a REST API endpoint as an alternative to a personal access token (PAT) for authentication. Private Key JWT assertion uses a private key instead of a client secret, so it does not require a password to be stored locally.
Set Up Authentication
-
Create an RSA key pair, which includes a public key file and a private key file.
NOTE: It is recommended to use a certificate authority (CA) certificate generated by a third party instead of a self-signed certificate.
Example: Self-signed certificate: openssl req -x509 -newkey rsa:4096 -keyout privkey.pem -out pubkey.pem -sha256 -days 365`
-
Submit a Support ticket to request a new client application registration be added to your OIS instance. Add the public key file (pubkey.pem) generated in step 1 as an attachment.
OneStream will send you a client ID and scope to include in the call to retrieve a JWT.
-
To retrieve a JWT from a console application, use the following table. Add the items from the Constant column and their values based on the guidance in the Set Value To column.
Constant | Set Value To | Example |
---|---|---|
OIS_ENDPOINT | OIS well-known configuration endpoint | https://<sitename>/onestreamis/.well-known/openid-configuration |
CLIENT_ID | Client ID received from OneStream | clientcred.testapp |
DESIRED_SCOPE | Scope received from OneStream | onestream.noninteractive.all |
KEY_FILE_LOCATION | File with location of private key | C:\\mycerts\\privkey.pem |
KEY_PASSWORD | Password for private key |
123Password |
TIP: The site name used in the OIS_ENDPOINT is in the Server Address used to connect to the Windows Application or Excel Add-In Clients.
NOTE: If you are unsure which OIS_ENDPOINT, CLIENT_ID, or DESIRED_SCOPE to use, contact OneStream Support.
This is a sample code in C# using a console application to retrieve a JWT:
internal class Program
{
private const string OIS_ENDPOINT = "https://localhost:44387/.well-known/openid-configuration";
private const string CLIENT_ID = "clientcred.testapp";
private const string DESIRED_SCOPE = "onestream.noninteractive.all";
private const string KEY_FILE_LOCATION = "C:\\mycerts\\privkey.pem";
private const string KEY_PASSWORD = "pass";
private static async Task Main(string[] args)
{
var rsaKey = System.Security.Cryptography.RSA.Create();
// this is an encrypted private key
rsaKey.ImportFromEncryptedPem(System.IO.File.ReadAllText(KEY_FILE_LOCATION), KEY_PASSWORD);
// if your private key doesn't start with ' --- BEGIN ENCRYPTED PRIVATE KEY ---', use ImportFromPem instead
//rsaKey.ImportFromPem(System.IO.File.ReadAllText(KEY_FILE_LOCATION));
var securityKey = new RsaSecurityKey(rsaKey);
var jwk = JsonWebKeyConverter.ConvertFromRSASecurityKey(securityKey);
var response = await RequestTokenAsync(new SigningCredentials(jwk, "RS256"));
Console.WriteLine("Got token: {0}", response.AccessToken);
Console.WriteLine("Expires in: {0}", response.ExpiresIn);
Console.WriteLine("Token type: {0}", response.TokenType);
Console.WriteLine("Scope: {0}", response.Scope);
}
private static async Task<TokenResponse> RequestTokenAsync(SigningCredentials signingCredentials)
{
var client = new HttpClient();
var disco = await client.GetDiscoveryDocumentAsync(OIS_ENDPOINT);
if (disco.IsError) throw new Exception(disco.Error);
var clientToken = CreateClientToken(signingCredentials, CLIENT_ID, disco.TokenEndpoint ?? throw new InvalidOperationException());
var response = await client.RequestClientCredentialsTokenAsync(new ClientCredentialsTokenRequest
{
Address = disco.TokenEndpoint,
ClientId = CLIENT_ID,
GrantType = OidcConstants.GrantTypes.ClientCredentials,
// this should be explicitly added as the default is via header, which we don't want with a client assertion
ClientCredentialStyle = ClientCredentialStyle.PostBody,
ClientAssertion =
{
Type = OidcConstants.ClientAssertionTypes.JwtBearer,
Value = clientToken
},
Scope = DESIRED_SCOPE
});
if (response.IsError) throw new Exception(response.Error);
return response;
}
private static string CreateClientToken(SigningCredentials credential, string clientId, string audience)
{
var now = DateTime.UtcNow;
var token = new JwtSecurityToken(
clientId,
audience,
[
new(JwtClaimTypes.JwtId, Guid.NewGuid().ToString()),
new(JwtClaimTypes.Subject, clientId),
new(JwtClaimTypes.IssuedAt, now.ToEpochTime().ToString(), ClaimValueTypes.Integer64)
],
now,
now.AddMinutes(1),
credential
);
var tokenHandler = new JwtSecurityTokenHandler();
return tokenHandler.WriteToken(token);
}
}
Call a Rest API Endpoint with a JSON Web Token
Use the JWT to call a REST API endpoint. See this Postman example:
-
In the Authorization tab, replace Token with the JWT.
-
In the Body tab, add the BaseWebServerUrl.
-
In the Params tab, add Key api-version and the version for Value (for example, 7.2.0).
-
In the Headers tab, add Key Content-Type and Value application/json.
-
View the sample response.