Authentication
The backend operates per-se in a stateless manner. This means that no request depends on any other data outside, the request itself, or any previous requests. The server does not store any vital information in the server-side session. This means that every REST API request must contain authentication information.
The Pricefx REST API supports the following types of authentication:
- Basic authentication (API Protocol – Version 1) – do not use in production environments for every request (can be used to obtain the JWT token)
- JWT token authentication (API Protocol – Version 1)
- External JWT token authentication (API Protocol – Version 1)
-
Auth-Token - PriceFx-Key
(API Protocol – Version 2)
Information
The name "API Protocol – Version 2" suggests that this version is "newer" or "better", it is not. It is simply different. So it is absolutely fine to use API Protocol – Version 1. The default user interface, the Excel Client, and many other systems rely on and use API Protocol – Version 1 authentication.
Basic Authentication
As the user's credentials are being sent in every HTTP request we do NOT recommend using this authentication method for production environments.
Note
The basic authentication leads to an extra 500 ms request execution time as the password verification is intentionally slow to mitigate brute-force password guess attacks. So in case you plan to run high volume API operations, please use the JWT token as the authentication mechanism.
How to Authenticate Using HTTP Basic Authentication
Each request must contain an Authorization
header, with a Base64 encoded value.
Authorization: Basic <Base64-encoded user data>
Base64-encoded user data must be in the following format:
<partition name>/<user name>:<password>
Basic Auth Example
Authorization of a dummy user on the mypartition
partition:
- Partition name: mypartition
- User name: john.doe
- Password: pass_123
Data to encode: mypartition/john.doe:pass_123
Base64-encoded user data: bXlwYXJ0aXRpb24vam9obi5kb2U6cGFzc18xMjM=
The authorization header that will be added to the request to get a response should then be as follows:
Authorization: Basic bXlwYXJ0aXRpb24vam9obi5kb2U6cGFzc18xMjM=
Setting Up Basic Authentication in Postman
Follow these steps to use Basic authentication in Postman:
- On the Authorization tab, select Basic Auth from the Type drop-down menu.
-
As
Username
, enter
<partitionName>/<yourUserName>
. -
Enter your password for the partition.
Information
When using Postman, you don't need to encode your credentials as it is done automatically by Postman.
JWT Token Authentication
Use the JWT (JSON Web Token) as a preferred method of authentication. The token can be either sent as an HTTP header or as a cookie and has an encoded expiration timestamp. JWT tokens can be invalidated on a per-user basis by changing the user's password.
Please note
JWT token is automatically issued (as a cookie) when an API call with Basic authentication (user/pwd) is called – it is highly recommended to use the JWT for subsequent calls (and not user/pwd on every API call).
Once the JWT nears its expiry, the server automatically re-issues (again as an updated cookie) a new JWT token value. The client should then replace the token on their end. Cookie-aware clients (such as browsers) do that automatically.
Information
You can use the /accountmanager.getjsonwebtoken endpoint to get a non-expiring JSON Web Token – used for integration purposes.
How To Authenticate Using JWT Token
- Make an API call to the desired partition using Basic authentication . You can use /login/extended endpoint for this purpose.
-
You will receive the
X-PriceFx-jwt
token as a cookie. -
Use the
X-PriceFx-jwt
token for every subsequent request.
Setting Up JWT Authentication in Postman
- On the Authorization tab, select API Key from the Type drop-down menu.
-
As
Key
, enter your
X-PriceFx-jwt
token. - Select the Header option in the Add to field.
External JWT Token Authentication
You can use a 3rd-party JSON Web Token for user authentication (can be used, for example, with Salesforce). Similarly to SAML, there is no need for hard-coded per-user credentials, instead a system-to-system trust relation is established using signed tokens.
Configuring the Trust Relationship
The configuration is stored in a per-partition configuration in Advanced Configuration Option (Administration > Configuration > System Configuration > Advanced Configuration Options) with the name externalJWTConfiguration
and the following value:
{
entries : {
<externalSystemName> : {
publicKey : <public key used by external system for signing in PEM format>
permissions: <null or JSON list of Strings with permissions names>
}
}
}
Where:
externalSystemName
is a JSON compatible name of the external system (i.e., only [A-Z a-z 0-9], no whitespaces, etc). This name is also used in the bearer auth header (see below). You can add as many external systems as you like, each with an individual name.
publicKey
is a string representation of the public key in the PEM format (see below for an example). Pricefx only supports RSA keys.
permissions
can be null (or omitted) – in that case, the trust does not further restrict permissions (= allow all). If there is a list defined, then any API call using that token/config will only be allowed if the endpoint’s permission requirement matches the permissions listed here.
Important
This permissions
list is just an additional filter. I.e., the authenticated user as such needs to have the required permission to begin with. This setting will not add or allow anything the user could do using regular authentication. It can be used to restrict the trust relation to specific API calls only.
Example of a public key:
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0E9Zp0JNbDaOMqhZd1C+
/FdBTCjza0qXcjTYCbDUeY89qPpwN19QovmehCVGBSFzLOkltx0YmlCkaKLtzqfS
...
... (edited for security reasons)
...
fXr4+6SBmEOVa7RSzoXr3whpdMdKsIvnDCCmT++aJvHBw63ZKGKb8+ZTAXv0z3sm
LDRyhifUmEoJPGWHV6/oxZQiVRapEFe7SiVTbr2IW7OfrdE3DVrioJmATEKgVr5i
zwIDAQAB
-----END PUBLIC KEY-----
Authenticating
Once the trust relationship is configured, you can use it to authenticate API calls. This is done by sending the signed (by the external system possessing the private key part) and compacted JWT token returned by the server in the standard Authorization
HTTP header in the following form:
Authorization: BEARER <externalSystemName>;<signed JWT token>
The JWT token follows standard RFC rules in terms of structure, signature, etc. Please note that only the RS256 signature method is supported.
The content of the token consists of three parts with a certain structure:
Header:
{
"alg": "RS256"
}
-
alg
identifies the algorithm used to generate the signature. It must always be set to RS256 – other algorithms are not supported.
Payload:
{
"sub": "root",
"iss": "AllowAll",
"aud": "integration-test",
"partition": "system"
}
-
sub
is the standard subject field of the JWT token. It must contain the loginName of the user. -
iss
is the standard issuer field of the JWT token. It must contain the external system name value (i.e., the same name as in the trust config and in the HTTP header). -
aud
is the standard audience field of the JWT token. It must contain the Pricefx cluster name. -
partition
must be set to the partition of the target & user.
Signature:
The signature part is a combination of the encoded header, the encoded payload, a secret, the algorithm specified in the header – all that signed.
API Protocol – Version 1 with Two-factor Authentication
On a per-user basis TFA can be enabled. If such a user should be used for a backend call (and no session cookie is present), an additional HTTP header with the current TFA token needs to be added. The TFA header looks like this:
PriceFx-TFA: {cleartext TFA Token}
The TFA token is usually a 6 digit number.
Auth-Tokens: API Protocol – Version 2
The main driver behind the v2 protocol was a more secure support for non-browser applications (e.g., mobile apps). Authentication by standard HTTP basic auth-headers or session cookies is banned.
Authentication is strictly done by means of so called "Auth-Tokens" that have a limited lifetime (which is not auto-extended as for session cookies). To get such an auth token (and an accompanying refresh token), a login procedure needs to be followed. This login then also may carry the need for custom request payload encryption to prevent man-in-the-middle attacks with spoofed CA certificates.
In general this scheme follows the OAuth principles and there are two ways of login procedure in v2: with or without device registration. The choice is not on the discretion of the client but bound to the Pricefx-Key that is used (and admin-configurable).
PriceFx-Key
The prerequisite to use the v2 protocol is a so-called "PriceFx-Key". This is a UUID string that is individual per client type. I.e. a specific version of an app or client. The key needs to be sent as a header value in every request and also defines (server side) a few protocol settings, like token lifetime, token refresh allowed, device registration required and so on. In order to retrieve such a key for your app or API client please contact Pricefx Support.
Pricefx-Key: {UUID}
Example:
Pricefx-Key: de305d54-75b4-431b-adb2-eb6b9e546014
Right now just the existence of such a header enables protocol v2. However, in future different keys (UUID values) may indicate an even higher protocol version. The value of the UUID is not fixed but is configurable and tied to a client version. Hence it can be controlled to allow/deny access of certain client types or versions.
Without Device Registration
Once you receive your Pricefx-Key
, use it to authenticate with access-token:
- Login
Use the POST /token
(Get an Authentication Token (API V2 only)) endpoint to retrieve an access-token, token-type, and refresh-token. Specific request details are described in the individual request (e.g., Pricefx API Reference > Authentication > Get an Authentication Token (API V2 only)).
The access-token will have a defined non-auto-prolonging lifetime and can be renewed by the refresh-token.
The authentication of subsequent requests is done by adding the access-token to every request:
Authorization: {access-token-type} {access-token}
- Refresh
Use the POST token/refresh
(Refresh an Authentication Token (API V2 only)) endpoint to renew the access token. The refresh-token does not have a lifetime, but is tied to the access-token. Specific request details are described in the individual request (e.g., Pricefx API Reference > Authentication > Refresh an Authentication Token (API V2 only)).
- Logout
Use the DELETE /token
(Delete an Authentication Token (API V2 only)] endpoint to invalidate the existing access token.
With Device Registration
If a Pricefx-Key requires a device registration, the login procedure looks slightly different. The end result (an access-token and access-token-type) for the subsequent call is the same though.
-
Create device registration POST
/pricefx/{partition}/locker
-
Refresh token POST
/pricefx/{partition}/token/refresh
-
Unlock device POST
/pricefx/{partition}/locker/unlock
-
Change registration POST
/pricefx/{partition}/locker/change
-
Delete registration DELETE
/pricefx/{partition}/locker
In order to authenticate, a device needs to be registered. Once the device is registered, the authorization is then tied to the device by a device fingerprint and protected by device specific secure storage. Sensitive information is NOT stored on the device though. Device-local storage is encrypted by AES. The AES key is stored on the server only and can only be obtained with the device specific unlock token (like a gesture, PIN, etc.). Step 1 results in the same access-token (next to other information) that can then be used for authenticating further requests (see above). It also includes the encryption key that the device must use to encrypt local data. This key MUST NOT be stored on the device, only kept in memory and securely discarded when the client application exits or goes to standby.
This access-token can be refreshed with Step 2. Once the device is locked or goes to standby, the user can unlock the device by device specific means (gesture, PIN, etc.) and then subsequently call the unlock command of Step 3. This will also generate a new access-token/refresh-token pair for subsequent requests. All requests to this endpoint include a mandatory in-request encryption.
Custom Request Encryption
All device registration related requests and responses are encrypted using AES and RSA. This is done in addition to the default HTTPS transport encryption. The rationale behind this is that the trust chain in the HTTPS world can be flawed (numerous examples in the recent past showed this) and the encryption can be broken with man-in-the-middle attacks. This can happen not due to broken encryption algorithms but due to the way the default browsers' trust chain is implemented. A custom end-to-end encryption layer using standard encryption algorithms and implementations adds an extra layer of security as the encryption is guaranteed to be end-to-end.
The following crypto protocols are used:
AES - 256 bit key - kCCOptionPKCS7Padding | kCCOptionECBMode
RSA - 2048 bit key - RSA/NONE/OAEPWithSHA1AndMGF1Padding
These crypto settings are tested and well working between Java world (e.g. Pricefx Backend, Android) and iOS. Only the request/response body is encrypted. List of encrypted requests:
POST /pricefx/{partition}/locker
DELETE /pricefx/{partition}/locker
POST /pricefx/{partition}/locker/unlock
POST /pricefx/{partition}/locker/change
The backend response is encrypted with the AES key which came from the request. Each request contains a different AES key.
Example
Client wants to send:
{ "hi" : "hallo" }
Pseudocode:
Note: Public_rsa_key is defined and configured per Pricefx-Key and known to the client.
json = "{ \"hi\" : \"hallo\" }"
json_data = json.get_utf8_bytes()
aes_key = generate_random_aes_256_key()
aes_key_encrypted = rsa_encrypt( api-key.public_rsa_key, aes_key )
aes_key_encrypted_b64 = base64encode( aes_key_encrypted )
json_data_encrypted = aes_encrypt( aes_key, json_data )
json_data_encrypted_b64 = base64encode( json_data_encrypted )
request_body = "{
"key" : $aes_key_encrypted_b64,
"data" : $json_data_encrypted_b64
}"
The server then uses the AES key from the request to decrypt initial JSON and same AES key to encrypt response JSON.
Pseudocode:
aes_key_encrypted_b64 = request["key"]
json_data_encrypted_b64 = request["data"]
aes_key_encrypted = base64decode( aes_key_encrypted_b64 )
aes_key = rsa_decrypt( api-key.private-rsa-key, aes_key_encrypted )
json_data_encrypted = base64decode( json_data_encrypted_b64 )
json_data = aes_decrypt( aes_key, json_data_encrypted )
// handle json_data JSON request
// response prepared in response_json
response_json_data = response_json.get_utf8_bytes()
response_json_data_encrypted = aes_encrypt( aes_key, response_json_data )
response_json_data_encrypted_b64 = base64encode( response_json_data_encrypted )
response_body = "{
"data" : $response_json_data_encrypted_b64
}"
The AES key is not sent back because the client has it for this response. The AES key is unique and randomly generated by the client for each request & response pair.
The request & response body is still JSON. The only difference is that the initial JSON is encrypted in data and the AES key is encrypted via RSA in the key. The private key is known to the backend and so no one can decrypt the request even if intercepted.
Cross-Site-Request-Forgery (CSRF) Protection
As the API is also used in browser applications, CSRF protection is often a required feature. It can be enabled/disabled per partition (by Support) and is generally turned on by default. If it is turned ON, every request that uses the session cookie to authenticate (i.e. requests carrying a valid Auth-header) needs also to send the CSRF token value in the request HTTP header:
X-PriceFx-Csrf-Token: {csrf-token}
The client can retrieve the token in two ways:
- From a cookie with the same name.
- From the JSON response payload of the first request after authentication.
Please note
The cookie will have the httponly
flag set. So this cookie will not be available from a JavaScript application within a browser for security reasons. For such an application, use the response payload.