Sessions

Sessions allow users to login using their username and password for doing requests to the API, in contrast to them having to sign their requests with their pre-shared key. This can for example be used to create a portal page where users can login to access their content without the portal needing to know their pre-shared keys. Sessions expire after some time of inactivity.

To be able to create sessions, an application with the session-create token is required. Creating a session is a two-step process implementing a challenge-response mechanism: first the session is initialized, resulting in a challenge which can then be used to actually create the session. Once the session is successfully created, it can be used to perform requests on behalf of the user. Finally, an active session can be deleted so it is unusable for further requests.

Note that it is not possible to create or delete sessions when the API is operating in read-only mode. Using existing sessions is possible, but the moment at which they time out will not be updated.

Creating a session

The first step in creating a session is to call the session/initialize action with the username for which to create a session and the IP of the client requesting the session. This IP is used for rate limiting.

If the given client IP is not rate limited, the session/initialize action returns a challenge and the required password salt. These values are also returned if the provided username is incorrect. An incorrect username or password is only reported when the calculated response is checked by the API.

The obtained challenge and salt need to be used with the password provided by the user to calculate a valid response. The method used to calculate the response is described below.

When the response has been calculated, it must be provided together with the challenge to the session/create action to actually create the session. This action returns either a general error, or success. The general error indicates that something went wrong when creating the session, which is usually an incorrect username or password, but can also indicate an implementation error such as not initializing the session or calculating the incorrect response.

Once the session is successfully created, a session ID and key are returned which can be used to perform requests within the newly created session, as well as the timeout for the session. The next section describes how to use the newly created session.

A challenge is only valid for a short period of time, roughly half a minute. The session/create API-action must be called with the correct response before the challenge expires.

Calculating the response

The response needs to be calculated with the following process, presented in pseudo-code:

intermediate := blowfish(md5(password), salt)
challenge_hash := sha256(sha256(intermediate) + challenge)
response := base64encode(challenge_hash (xor) intermediate)

First an intermediate value is calculated by first taking the MD5-hash of the user-provided password, and then calculating the Blowfish hash of that MD5-hash using the provided salt. Next, the SHA-256 hash of this intermediate value is calculated, the challenge is appended to the resulting hash, and the SHA-256 hash of that entire string is calculated. The resulting challenge hash is bit-wise XOR’d with the intermediate value to obtain a binary representation of the response, which is base64-encoded to obtain the actual response.

A PHP-function which calculates the response given the password, salt, and challenge is as follows:

function calculateResponse($password, $salt, $challenge)
{
   $intermediate = crypt(md5(password), $salt);
   $challenge_hash = hash('sha256', hash('sha256', $intermediate) . $challenge);
   return base64_encode($challenge_hash ^ $intermediate);
}

Note that the PHP crypt function uses a different hashing algorithm based on the provided salt. All salts provided by the API start with $2y$??$, where ?? is a 2-digit number representing the number of Blowfish rounds to use. This salt ensures that the PHP crypt function uses the correct Blowfish algorithm.

Using a session

Once a session has been successfully created, it can be used to do requests on behalf of the logged in user. Doing so requires extra authentication on top of the required application authentication. To indicate that a session is in use, the session ID must be provided as the session parameter, and the session key must be appended to the application key used for signing requests.

For example, consider the following request, which does not yet have a signature:

Server: http://api.streamone.nl
Path: /api/item/view
Parameters:
api 3
format json
authentication_type application
application Cmv8fnKfjF2l
timestamp 1386332263
Arguments: id=GagMfaiZClaE&archived=1

We want to use a session to perform this request, and we have the following values for the session and the application:

Application ID: Cmv8fnKfjF2l
Application PSK: ApplicationPSK
Session ID: BQokYIpLCMIE
Session key: SessionKey

A session paramater must be added which contains the session ID, and the session must be signed using the normal authentication process, but using a key consisting of the concatenation of the application PSK and session key. In this example, that key will be ApplicationPSKSessionKey.

Signing this request results in the following signed request, which contains additional session and signature parameters:

Server: http://api.streamone.nl
Path: /api/item/view
Parameters:
api 3
format json
authentication_type application
application Cmv8fnKfjF2l
session BQokYIpLCMIE
timestamp 1386332263
signature 7298eeeb29488574b219f5b6c5aaba2a4698d182
Arguments: id=GagMfaiZClaE&archived=1

Deleting a session

When a user logs out, the corresponding session needs to be deleted. Doing so is easy: just perform an API request to the session/delete API-action which is signed with the session to log out. Once this API request returns successfully, the session is deleted and can no longer be used to perform requests.

This API request can fail if the provided session was already deleted or timed out. In this case, the goal of having an unusable session is also reached: it was not usable to delete the session in the first place.

Rate limiting

The session API natively provides rate limiting for incorrect logins. This prevents successfully using brute-force to determine a user’s password.

Rate limiting is done on IP basis, and requires passing the IP of the client currently logging in to the session/initialize API-action. When there are too many non-successful login attempts from that IP address, new login attempts will be rate limited. When this happens, the session/initialize API-action will return STATUS_RATE_LIMITED instead of a challenge and salt.

Rate limiting will remain active for a period of time in the order of minutes. After this period, the user can perform new, hopefully successful, login attempts from their IP.

It is important to pass the correct client IP to the API for the purpose of rate limiting. If, for example, the server IP is sent instead, a single malicious user can prevent everyone from logging in.

Timeouts

A session that is unused for some time becomes invalid. The time before a session times out is at least 15 minutes, but can be more than that to account for internal processes in the API regarding session management.

When a session has timed out, it can no longer be used to perform requests. The session will be deleted, and any further requests signed with that session will result in an authentication error.

Compatibility with API version 2

To allow more secure password management for users in API version 3, a backwards-incompatible change was introduced in the password storage. The process described above works with passwords that are stored in a way suitable for use with API version 3, but those passwords are not usable with API version 2.

To ensure interoperability between these API versions, it may be required to specify a password in a format compatible with API version 2 when a session is created. By doing so, a user that has logged in or changed their password via API version 3 can always login using API version 2.

The system guarantees that in the following cases a password hash compatible with API version 2 is never required:

  • when the user never logs in via an application using API version 2; or
  • when the user has previously successfully logged in using API version 3, and their password was not changed via API version 2 since.

There is also some compatibility code in API version 2 to convert passwords when required. However, this provides no further guarantees than those stated above.

The session/initialize API-action returns a field named needsv2hash, which is true if and only if a API version 2-compatible password hash is required. If this value is true, the argument v2hash needs to be provided to the session/create API call.

The v2hash field must contain the unsalted MD5-hash of the user’s password. As stated above, it is never required to send this unsalted MD5-hash if API version 2 is never used.