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

  1. 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`

  2. 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.

  3. 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:

Copy
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:

  1. In the Authorization tab, replace Token with the JWT.

    The Authorization tab is selected, and the Token field is filled in with a JSON Web Token.

  1. In the Body tab, add the BaseWebServerUrl.

    The Body tab is selected, and the BaseWebServerUrl is added as http://localhost:3403/OneStreamWeb

  2. In the Params tab, add Key api-version and the version for Value (for example, 7.2.0).

    The Params tab is selected, and api-version is added as a key with a value of 7.2.0.

  3. In the Headers tab, add Key Content-Type and Value application/json.

    The Header tab is selected, and Content-Type is added as a key with a value of application/json.

  4. View the sample response.

    The sample request shows a Message of Logon succeeded.