TimeBase Web Administrator supports two types of authentication: built-in OAuth2 & SSO. One of those types must be enabled to run the application.
To enable built-in authentication, you need to add the following
security block to your application.yaml
configuration file.
We recommend using this authentication method for test purposes.
security:
oauth2:
provider:
providerType: BUILT_IN_OAUTH
clientId: web
tokenEndpoint: /oauth/token
secret: <BCrypt_encoded_secret>
authorizedGrantTypes:
- password
- refresh_token
users: # list of users with its authorities
- username: <username>
password: <BCrypt_encoded_password>
authorities: [TB_ALLOW_READ, TB_ALLOW_WRITE]
scopes:
- trust
accessTokenValiditySeconds: 300 # 5 min
refreshTokenValiditySeconds: 86400 # one day
privateKey: |
-----BEGIN RSA PRIVATE KEY-----
<RSA private key>
-----END RSA PRIVATE KEY----- |
publicKey: |
<RSA public key>
To enable SSO with ORY Hydra
add the following blocks to your application.yaml
configuration file.
spring:
security:
oauth2:
resourceserver:
jwt:
issuer-uri: # Issuer URI
...
security:
oauth2:
provider:
providerType: SSO
name: hydra
clientId: <client_id> # Your client ID
validateIssuer: false
userInfo:
enable: true
In this section we describe how to configure TimeBase Web Admin Authentication with Auth0 authentication service provider.
<tbwa_base_url>/assets/sign-in.html
,
<tbwa_base_url>/assets/silent-auth.html
<tbwa_base_url>//assets/sign-in.html
<tbwa_base_url>
<tbwa_base_url>
Add the following variables to TimeBase chart in TimeBase Web Admin section:
SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_ISSUER-URI: https://<your_domain>.auth0.com/
SECURITY_OAUTH2_PROVIDER_VALIDATEISSUER: true
SECURITY_OAUTH2_PROVIDER_USERINFO_ENABLE: true
SECURITY_OAUTH2_PROVIDER_CLIENTID: <client_id>
SECURITY_OAUTH2_PROVIDER_CLIENTSECRET: <secret>
SECURITY_OAUTH2_PROVIDER_PROVIDERTYPE: SSO
SECURITY_OAUTH2_PROVIDER_NAME: auth0
SECURITY_OAUTH2_PROVIDER_AUDIENCE: <api_audience>
SECURITY_OAUTH2_PROVIDER_CONFIGURL: https://<your_domain>.auth0.com/.well-known/openid-configuration
SECURITY_OAUTH2_PROVIDER_LOGOUTURL: https://<your_domain>.auth0.com/logout
SECURITY_OAUTH2_USERS_0_USERNAME: "<username>"
The Api Keys library supports two flows of accessing API with API Keys:
Each Api Key is a pair: ApiKey and ApiSecret. ApiKey is a name of the pair, and ApiSecret is used to get a query’s signature. Signature is used to verify that a query was signed with a valid ApiSecret.
Provide two headers to send requests with API Keys:
X-Deltix-ApiKey: # ApiKey
X-Deltix-Signature: # signature
where
X-Deltix-ApiKey
: your ApiKeyX-Deltix-Signature
: a payload, signed by ApiSecret with
hmac sha384 algorithm
Payload
= uppercase(HttpMethod) + lowercase(UrlPath) +
QueryParameters + body, where
QueryParameters
is separated by &
lowercase(key)=value
pairs, sorted alphabetically by
keysExample
To send query:
GET http://localhost:8099/api/v0/charting/bbo?startTime=2009-06-19T19:22:00.000Z&endTime=2009-06-19T19:25:00.000Z&symbols=AAPL&levels=1&maxPoints=6000&type=TRADES_BBO
ApiSecret
= your ApiSecretPayload
=
GET/api/v0/charting/bboendtime=2009-06-19T19:25:00.000Z&levels=1&maxpoints=6000&starttime=2009-06-19T19:22:00.000Z&symbols=AAPL&type=TRADES_BBO
Signature = 7amMhPgGq2mXo6twDUyDUlWAYJ9g+PyemZ1yIj6yhCnk4TS5viVi9DCGpaWX+GZz
.GET http://localhost:8099/api/v0/charting/bbo?startTime=2009-06-19T19:22:00.000Z&endTime=2009-06-19T19:25:00.000Z&symbols=AAPL&levels=1&maxPoints=6000&type=TRADES_BBO
X-Deltix-ApiKey: your ApiKey
X-Deltix-Signature: 7amMhPgGq2mXo6twDUyDUlWAYJ9g+PyemZ1yIj6yhCnk4TS5viVi9DCGpaWX+GZz
Example with Body
POST http://localhost:8099/api/v0/bars1min/goog/select
{
"from":null,
"to":null,
"offset":0,
"rows":1000,
"reverse":false,
"space":null,
"types": ["deltix.timebase.api.messages.BarMessage"]
}
Use:
ApiSecret
= your ApiSecretPayload
=
POST/api/v0/bars1min/goog/select{“from”:null,“to”:null,“offset”:0,“rows”:1000,“reverse”:false,“space”:null,“types”:[“deltix.timebase.api.messages.BarMessage”]}To calculate a Signature via Base64EncodedString(HmacSHA384(Payload, ApiSecret)):
Signature = DtMdHJ4vc0LYx9H0YB80dICiah10x/i1KFrJ+Ba+RyOw5wc+6WcXdxCHA3GFYrIe
Pass two headers with POST request:
POST http://localhost:8099/api/v0/bars1min/goog/select{"from":null,"to":null,"offset":0,"rows":1000,"reverse":false,"space":null,"types":["deltix.timebase.api.messages.BarMessage"]}
X-Deltix-ApiKey: your ApiKey
X-Deltix-Signature: DtMdHJ4vc0LYx9H0YB80dICiah10x/i1KFrJ+Ba+RyOw5wc+6WcXdxCHA3GFYrIe
Provide 3 STOMP headers to connect to WebGateway with websockets:
X-Deltix-ApiKey
X-Deltix-Payload
X-Deltix-Signature
where
ApiKey
: your ApiKeyPayload
: random stringSignature
: signature value calculated using
Base64EncodedString(HmacSHA384(Signature payload, ApiSecret))
Signature payload
: CONNECTX-Deltix-Payload= +
HeaderValue(X-Deltix-Payload) + “&X-Deltix-ApiKey=” +
HeaderValue(X-Deltix-ApiKey)ApiSecret
: your ApiSecretExample
Take:
ApiKey
= your ApiKeyApiSecret
= your ApiSecretPayload
(random string) =
90dd333e-4858-4fba-a71b-12f958b36689Signature payload
= CONNECTX-Deltix-Payload=
90dd333e-4858-4fba-a71b-12f958b36689&X-Deltix-ApiKey=yourApiKeySignature
=
nAoVRNtR+g8gKUG6/4hQbBbRy6A9KcqGfBjIx1gZCfwrGkvHBelJIpzosxelRRGFConnect a STOMP query:
CONNECT
X-Deltix-ApiKey: your ApiKey
X-Deltix-Payload: 90dd333e-4858-4fba-a71b-12f958b36689
X-Deltix-Signature: nAoVRNtR+g8gKUG6/4hQbBbRy6A9KcqGfBjIx1gZCfwrGkvHBelJIpzosxelRRGF
heart-beat:0,0
accept-version:1.1,1.2
Python REST Query Sample
import requests
import hashlib
import hmac
import base64
= "TEST_API_KEY"
apiKey = "TEST_API_SECRET"
apiSecret = "GET/api/v0/streams"
payload = base64.b64encode(hmac.new(apiSecret.encode('utf-8'), payload.encode('utf-8'), hashlib.sha384).digest())
signature
= {'X-Deltix-ApiKey' : apiKey, 'X-Deltix-Signature' : signature}
headers = requests.get("http://localhost:8099/api/v0/streams", headers=headers)
response
print(response)
print(response.json())
Node.js REST Query Sample
const http = require('http');
var crypto = require('crypto');
var hmac = crypto.createHmac('sha384', 'TEST_API_SECRET');
.write('GET/api/v0/streams');
hmac.end();
hmac= hmac.read().toString('base64');
signature
var options = {
headers: {
'X-Deltix-ApiKey': 'TEST_API_KEY',
'X-Deltix-Signature': signature
};
}
= http.get('http://localhost:8099/api/v0/streams', options, function(res) {
request var body = "";
.on('data', function(data) {
res+= data;
body ;
}).on('end', function() {
resconsole.log(body);
}).on('error', function(e) {
res.log("Got error: " + e.message);
onsole;
}); })
Java REST Query Sample
When Api Keys library is configured to use sessions, server does not store any private (secret) keys. In this case, client and secret must perform a login procedure to create a session with a secret key shared only between the client and the server.
Session includes two steps:
In this step the Client sends an attempt POST request to the Web server.
POST /api/v1/login/attempt
Request details:
api_key_id
[string] - API key identifier that is going
to be used for creating the session.Response details:
session_id
[string] - Unique session identifier
generated by the server;challenge
[string] - Random string generated by the
server that is used for user validation, encoded as base64;dh_base
[string] - String containing Diffie–Hellman
public base, encoded as base64;dh_modulus
[string] - String containing Diffie–Hellman
public modulus, encoded as base64;ttl
[string] - Number of milliseconds defining the time
when session will be dropped if no confirmation comes.In this step the Client sends a confirmation POST request to the Web server.
POST /api/v1/login/confirm
Request details:
session_id
[string] - Unique session identifier
generated by the server;signature
[string] - String, containing a
base54-encoded signature generated using SHA256withRSA of challenge
string provided to the client during login attempt and a private
key
of the API key, which identifier was sent to the server
during login attempt;dh_key
[string] - String containing Diffie–Hellman key
of the client, encoded as base64.Response details:
dh_key
[string] - String containing client’s
Diffie–Hellman public key, encoded as base64;keepalive_timeout
[string] - Inactivity period after
which the session will be terminated by the server in milliseconds.Upon the successful completion of this step, both the Client and the Server have enough data to generate Session Secret using Diffie–Hellman method. Session Secret is used for signing requests - see the following section.
Each REST and Websocket CONNECT request must be signed using a session secret. Web server will compute the signature on such requests and, if the result is different from the signature provided, the request will be rejected.
Include three headers in the request:
X-Deltix-Nonce
- is a number called nonce
.
Each subsequent request within a single session must have
nonce
value greater than the previous request
nonce
value. If the request contains the same or lower
nonce
value than the previous request, such request will be
rejected;X-Deltix-Session-Id
- session identifier created during
the login. This must be equal to the session_id
returned by
the login attempt;X-Deltix-Signature
- signature
string
.Where Signature
is calculated as follows
Base64EncodedString(HmacSHA384(Payload, SessionSecret)), where +
Payload
= uppercase(HttpMethod) + lowercase(UrlPath) +
QueryParameters + RequestHeaders + body * where
QueryParameters
is separated by ‘&’
lowercase(key)=value pairs, sorted alphabetically by key * and
RequestHeaders
= X-Deltix-Nonce=…&X-Deltix-Session-Id=…
+ SessionSecret
generated after the login procedure.
Provide 3 STOMP headers to connect to WebGateway with websockets:
X-Deltix-Session-Id
X-Deltix-Signature
X-Deltix-Nonce
where
X-Deltix-Session-Id
: Your session idX-Deltix-Signature
:
Base64EncodedString(HmacSHA384(Payload, SessionSecret))
Payload
: “CONNECTX-Deltix-Nonce=” + nonce +
“&X-Deltix-Session-Id=” + sessionIdSessionSecret
: generated after the login procedureX-Deltix-Nonce
:a number called nonce
. Each
subsequent request within a single session must have nonce
value greater than the previous request nonce
value. If the
request contains the same or lower nonce
value than the
previous request, such request will be rejected;Example
CONNECT
X-Deltix-Session-Id: Your session id
X-Deltix-Signature: Signature
X-Deltix-Nonce: 1000
heart-beat:0,0
accept-version:1.1,1.2
Java Sample
public class Main {
public static void main(String[] args) throws IOException, URISyntaxException, InterruptedException, NoSuchAlgorithmException, SignatureException, InvalidKeySpecException, InvalidKeyException {
= new Gson();
Gson gson Map<String, String> config = gson.fromJson(new FileReader("../config.json"), Map.class);
= HttpClient.newHttpClient();
HttpClient client = HttpRequest.newBuilder()
HttpRequest attemptRequest .uri(URI.create(String.format("https://%s/session/login/attempt", config.get("host"))))
.POST(HttpRequest.BodyPublishers.ofString(
String.format("{\"api_key_id\": \"%s\"}", config.get("api_key_id"))
))
.header("Content-Type", "application/json")
.build();
Map<String, String> attemptResponse = gson.fromJson(client.send(attemptRequest, HttpResponse.BodyHandlers.ofString()).body(), Map.class);
Signature signatureCreator = Signature.getInstance("SHA256withRSA");
.initSign(KeyFactory.getInstance("RSA").generatePrivate(new PKCS8EncodedKeySpec(
signatureCreator.getDecoder().decode(config.get("api_key_private"))
Base64)));
.update(Base64.getDecoder().decode(attemptResponse.get("challenge")));
signatureCreatorbyte[] signature = signatureCreator.sign();
BigInteger dhBase = new BigInteger(Base64.getDecoder().decode(attemptResponse.get("dh_base")));
BigInteger dhModulus = new BigInteger(Base64.getDecoder().decode(attemptResponse.get("dh_modulus")));
BigInteger dhSecretInteger = new BigInteger(512, new SecureRandom());
System.out.println("Successfully started login attempt");
Map<String, String> confirmationBody = new HashMap<>();
.put("dh_key", Base64.getEncoder().encodeToString(dhBase.modPow(dhSecretInteger, dhModulus).toByteArray()));
confirmationBody.put("signature", Base64.getEncoder().encodeToString(signature));
confirmationBody.put("session_id", attemptResponse.get("session_id"));
confirmationBody= HttpRequest.newBuilder()
HttpRequest confirmationRequest .uri(URI.create(String.format("https://%s/session/login/confirm", config.get("host"))))
.POST(HttpRequest.BodyPublishers.ofString(gson.toJson(confirmationBody)))
.header("Content-Type", "application/json")
.build();
Map<String, String> confirmationResponse = gson.fromJson(client.send(confirmationRequest, HttpResponse.BodyHandlers.ofString()).body(), Map.class);
BigInteger sessionSecret = new BigInteger(Base64.getDecoder().decode(confirmationResponse.get("dh_key"))).modPow(dhSecretInteger, dhModulus);
System.out.println("Successfully confirmed a session login");
Mac mac = Mac.getInstance("HmacSHA384");
.init(new SecretKeySpec(sessionSecret.toByteArray(), "Raw Bytes"));
macString payload = "GET"+config.get("test_request")+"X-Deltix-Nonce=1&X-Deltix-Session-Id="+attemptResponse.get("session_id");
String requestSignature = Base64.getEncoder().encodeToString(mac.doFinal(payload.getBytes()));
= HttpRequest.newBuilder()
HttpRequest testRequest .uri(URI.create(String.format("https://%s%s", config.get("host"), config.get("test_request"))))
.header("X-Deltix-Signature", requestSignature)
.header("X-Deltix-Nonce", "1")
.header("X-Deltix-Session-Id", attemptResponse.get("session_id"))
.GET()
.build();
System.out.println(client.send(testRequest, HttpResponse.BodyHandlers.ofString()).body());
}
}
JavaScript Sample
const https = require('https');
const http = require('http');
const crypto = require('crypto');
const bigintCryptoUtils = require('bigint-crypto-utils');
const fetch = async (url, method = 'GET', body = null, headers = {}) => {
return new Promise((resolve, reject) => {
const request = (url.startsWith('https:') ? https : http).request(url, {
,
methodheaders: {
'Content-Type': 'application/json',
...headers,
,
}, (response) => {
}
.on('data', (buffer) => {
responseif (response.statusCode >= 200 && response.statusCode < 400) {
resolve(buffer.toString('utf-8'));
else {
} reject(new Error(response.statusCode + buffer.toString('utf-8')));
};
});
})
if (body) {
.write(JSON.stringify(body))
request
}
.on('error', reject);
request
.end();
request;
});
}/**
* Convert base64 string to BigInt
* @param {string} base64Str
*/
const fromBase64 = (base64Str) => BigInt('0x' + Buffer.from(base64Str, 'base64').toString('hex'));
/**
* Makes Java specific conversion of BigInt to Buffer
* @param {BigInt} bigInt
*/
const bigIntToBuffer = (bigInt) => {
const hex = bigInt.toString(16);
// For Java BigInts the length of byte[] representation of BigIntegers should be exactly (ceil((number.bitLength() + 1)/8)) so we right-pad the number with 0s
const str = '0'.repeat(Math.ceil((bigInt.toString(2).length + 1) / 8) * 2 - hex.length) + hex;
return Buffer.from(str, 'hex');
;
}
/**
* Convert BigInt to base64 string
* @param {BigInt} bigInt
*/
const toBase64 = (bigInt) => bigIntToBuffer(bigInt).toString('base64');
const main = async () => {
const singInAttemptResponse = await fetch(`http://localhost:8099/session/login/attempt`, 'POST', {
api_key_id: 'TEST_SESSION_API_KEY',
;
})
const singInAttempt = JSON.parse(singInAttemptResponse);
const dhModulus = fromBase64(singInAttempt.dh_modulus);
const dhBase = fromBase64(singInAttempt.dh_base);
const privateKey = crypto.createPrivateKey({
key: `-----BEGIN PRIVATE KEY-----\nMIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAOr9j+QRqD28+V8+7Z3MVR649Nlf3iDzm/8vdPFG9ceZHUhC2M5I8K1jg2bN4tvesvB/Qnb5fwd4LcW9rqhFWXmGitrvtw5OYu5OYRl7qhXGMW91GxCp9xSUCqKNWKI8yWcNBn8ewLpLtYtnIzBq11sGwW2dtP19vebhUN5qRRVDAgMBAAECgYAwP3+bxERW6MYK2FDRZXLUrAUZ3KUu/tW4v3WzVG6CXN22SINbV36TGyuPoBZELqVu27I522BJmFNNlnSV+Cc2d7+Je/LnyH853DNQu3QqlsBLzUEWt0KqCLjKF1BdVxALD0ddGka3RIAsjTJnxDVLVagfqxVOXcg/pxtrFvkMgQJBAPg1+J+dD71EocoNaSd0rsGtMEHSSiT2Dyfi9JJHHCooZ8pEJs6WtCH0Qc0xA4NQ/+EV7Zqg74J9fSrkPXxI0/8CQQDyXWI/H7T9WeqWVxh0/ZUUI2Y1x1SD6Y7LYNprzT/raUBqSPVaIv5W+A8057s80AeIiLJ7OLUJvKggcvqul269AkBiLObUK0mIcVcVFkzbYFmnHZuSzVyqVfEUs75NBXdsbWLwLBi1agKB050bTiG3lRhArW231aQmlwAlMPXo7N19AkBU7nCdWkkcd0QDxyWk6bAyTG1m7yEo0NHfZ2NjX5vErS+Lj2GbYqPqaic6DPLKTsQ1DmItWCPo85mfNWuvfxWpAkEAxX3/9QJQefjsfZvk77tLZZRM8aUI/O2YnT5ex1oufzeXmdVZpZ3f427pnosRAHZwFPvL3g8oh1iK8ynAm11EMA==\n-----END PRIVATE KEY-----`,
format: 'pem',
type: 'pkcs8'
;
})
const signer = crypto.createSign('RSA-SHA256');
.write(Buffer.from(singInAttempt.challenge, 'base64'));
signerconst signature = signer.sign(privateKey, 'base64');
const buffer = crypto.randomBytes(512);
const dhNumber = BigInt('0x' + buffer.toString('hex'));
const dhKey = bigintCryptoUtils.modPow(dhBase, dhNumber, dhModulus);
const signInResponse = await fetch(`http://localhost:8099/session/login/confirm`, 'POST', {
session_id: singInAttempt.session_id,
,
signaturedh_key: toBase64(dhKey),
;
})
const signIn = JSON.parse(signInResponse);
const signKey = bigintCryptoUtils.modPow(fromBase64(signIn.dh_key), dhNumber, dhModulus);
const secretKey = crypto.createSecretKey(bigIntToBuffer(signKey));
const nonce = Date.now();
const payload = `GET/api/v0/streamsX-Deltix-Nonce=${nonce}&X-Deltix-Session-Id=${singInAttempt.session_id}`;
const requestSignature = crypto.createHmac('SHA384', secretKey).update(payload).digest('base64');
const brokersResponse = await fetch(`http://localhost:8099/api/v0/streams`, 'GET', void 0, {
'X-Deltix-Nonce': nonce,
'X-Deltix-Session-Id': singInAttempt.session_id,
'X-Deltix-Signature': requestSignature,
;
})
console.log(brokersResponse);
;
}
return main();