Skip to main content

Security

Password hashing

User accounts store a password hash, never the plaintext password. Hashing uses PHP’s password_hash() with the PASSWORD_ARGON2I algorithm. The hash is kept in the internal _passwordHash property, which - like all underscore-prefixed properties - can never be read or written directly through the API.

A user’s password is only ever needed to obtain a new token via POST /token or during POST /register. Regular requests authenticate with a token instead, as described below.

Token format and hashing

Tokens are generated by the API, not chosen by the user, and follow the secret-token: URI scheme from RFC 8959:

secret-token:<base58(16 random bytes)>

The plaintext token is only ever sent to the client once, in the response to the request that created it. Internally, only its SHA-3-256 hash is stored. Unlike passwords, tokens don’t need a slow, salted password-hashing algorithm - a random 16-byte value already has enough entropy that brute-forcing the hash is infeasible.

Authenticating a request

Every request is evaluated as one of the following:

  • No Authorization header: the request is treated as coming from the anonymous user.
  • A well-formed secret-token:... bearer token: the API hashes the token and looks it up, first in a Redis cache, then - on a cache miss - in the graph database, where the cache is refilled for subsequent requests.
  • A malformed, unknown, expired or revoked token: the request is rejected with 401 Unauthorized. An invalid token is never silently downgraded to an anonymous request - if a request supplies a token, it only ever executes in that token’s context, or fails outright.

Token lifecycle

Tokens generated through the API have a default lifetime of 3 hours, which can be changed to a duration between 30 minutes and 13 months.

The limits are configurable.

Tokens can become immortal, when the property expirationDate is absent. This can not happen through API requests, i.e. it requires the import of a specially crafted backup or direct manipulation of the token inside the Neo4j database.

Revoking tokens

Should multiple or all tokens need to be revoked, then command token:revoke can be used. It supports filtering by user, group, and issue date, and can run as a dry run first to preview which tokens would be affected.

Revocation takes effect immediately: the token’s cache entry is cleared as part of the same command, so a revoked token stops working right away rather than only after its cached entry would have expired on its own.

Rate limits

Rate limiting is a planned feature and not yet enforced - the API does not currently reject requests for exceeding a request rate.

Reserved data types

A handful of node types are reserved for the API itself and can’t be used as the type of an element you create: User, Token, and Upload. Attempting to create an element with one of these types is rejected.

More generally, any property or type name starting with an underscore (_) is treated as internal to the API - for example the _passwordHash property on User nodes. These are set and read by the API itself and are never exposed through regular read/write operations. Sticking to non-underscore-prefixed names for your own data avoids clashes with current or future internal properties.

Reserved properties

A handful of non-underscore-prefixed properties are also reserved: the API sets them itself as a side effect of other operations, and rejects any POST /<uuid>/PUT /<uuid>/ PATCH /<uuid> request that tries to set them directly, with 400 Bad Request.

On every element:

  • id - assigned once, when the element is created.
  • created / updated - timestamps maintained automatically: created is set once, updated on every change.
  • file / hasFile - set as a side effect of uploading, replacing, or deleting the element’s file; see Metadata in the file reference.

Additionally, on User nodes: the property configured as the registration identifier (email by default). On Token nodes: token.