This is the API that drives BloodHound Enterprise and Community Edition. Use it to extend the use of the BloodHound product to work with other tools in your environment.

Endpoint availability is noted using the Community and Enterprise tags.

To get help with BloodHound Community Edition, join our Slack community. BloodHound Enterprise customers can submit tickets.

Authentication

The BloodHound API supports two kinds of authentication: JWT bearer tokens and signed requests.

For quick tests or one-time calls, the JWT used by your browser may be the simplest route. For more secure and long lived API integrations, we recommend using signed requests.

JWT bearer token

The API accepts calls using the following header structure in the HTTP request:

Authorization: Bearer $JWT_TOKEN

If you open the Network tab in your browser, you’ll see calls against the API made using this structure. JWT bearer tokens are supported by the BloodHound API, but they should only be used for temporary access. JWT tokens expire after a set amount of time and require re-authentication using secret credentials.

Signed requests

Signed requests are the recommended form of authentication for the BloodHound API. Not only are signed requests better for long lived integrations, they also provide more security for the requests being sent. They provide authentication of the client, as well as verification of request integrity when received by the server.

Signed requests consist of three main parts: The client token ID, the request timestamp, and a base64 encoded HMAC signature. These three pieces of information are sent with the request using the following header structure:

Authorization: bhesignature $TOKEN_ID
RequestDate: $RFC3339_DATETIME
Signature: $BASE64ENCODED_HMAC_SIGNATURE

To use signed requests, you need to generate an API token. Each API token generated in the BloodHound API comes with two parts: The Token ID, which is used in the Authorization header, and the Token Key, which is used as part of the HMAC hashing process. The token ID should be considered as public (like a username) and the token key should be considered secret (like a password). Once an API token is generated, you can use the key to sign requests.

For more documentation about how to work with authentication in the API, including examples of how to generate an API token in the BloodHound UI, see Working With the BloodHound API.

Example: Signed request pseudo-code

First, a digest is initiated with HMAC-SHA-256 using the token key as the digest key:

digester = hmac.new(sha256, api_token_key)

OperationKey is the first HMAC digest link in the signature chain. This prevents replay attacks that seek to modify the request method or URI. It is composed of concatenating the request method and the request URI with no delimiter and computing the HMAC digest using the token key as the digest secret:

# Example: GET /api/v2/test/resource HTTP/1.1
# Signature Component: GET/api/v2/test/resource
digester.write(request_method + request_uri)

# Update the digester for further chaining
digester = hmac.New(sha256, digester.hash())

DateKey is the next HMAC digest link in the signature chain. This encodes the RFC3339 formatted datetime value as part of the signature to the hour to prevent replay attacks that are older than max two hours. This value is added to the signature chain by cutting off all values from the RFC3339 formatted datetime from the hours value forward:

# Example: 2020-12-01T23:59:60Z
# Signature Component: 2020-12-01T23
request_datetime = date.now()
digester.write(request_datetime[:13])

# Update the digester for further chaining
digester = hmac.New(sha256, digester.hash())

Body signing is the last HMAC digest link in the signature chain. This encodes the request body as part of the signature to prevent replay attacks that seek to modify the payload of a signed request. In the case where there is no body content the HMAC digest is computed anyway, simply with no values written to the digester:

if request.body is not empty:
digester.write(request.body)

Finally, base64 encode the final hash and write the three required headers before sending the request:

encoded_hash = base64_encode(digester.hash())
request.header.write('Authorization', 'bhesignature ' + token_id)
request.header.write('RequestDate', request_datetime)
request.header.write('Signature', encoded_hash)