[RFC] Tokens (updated and extracted from labels)

What follows is an updated RFC for Tokens. Separated out from the previous Label/Token/RFC for clarity. (Labels will get its own updated RFC shortly).

The only practical change from the prior RFC beyond the increased clarity is a need to determine what is the ‘current’ token (due to UX updates around permissions), and so the inclusion of a TokenHashes storage for ClientHandler access.


  • Status: proposed
  • Type: new authentication mechanism
  • Related components: SCL, safe-api, authenticator, safe-nd
  • Start Date: 28/01/2020
  • Supersedes: Tokens/Labels/Auth


Changes to authentication mechanisms needed for Labels implementation.

This is a macaroon-like, ‘bearer token’ system, utilising BLS to manage permissions and advanced caveats for application permissions. (Limitations in the macaroon implementation make actual macaroon use non-viable for SAFE workflows.)

This enables us to validate data access not only in the Client Handling code, against generic permissions, but also at the Data Handling layer, validating tokens against specifics of that data.


  • The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119.


Knowledge of BLS asymmetric encryption is assumed, as well as SAFE request validation, and ClientHandler, DataHandler and Authenticator flows.


Enables checking of arbitrary App’s permissions / caveats based upon a token which can be passed through to vaults, and therefore checked against the data itself.

Simplifies adding/updating permissions to the token struct, and checking those becomes only a matter of (optionally) verifying the desired caveat.

Detailed design

Data operations

To access any non public data, a (non revoked) application MUST present a token which is

  1. Valid
  2. Current (as determined by token sha3-256 hashing) against AppTokenHashes struct
  3. Be valid for relevant caveats at ClientHandling (this could be expiry, or permission checks against the current request for mutations etc)


  1. Have an appropriate SignedLabelShare caveat to be validated at DataHandling.

Data storage

There are three key locations relevant for token storage.

  • AccessContainer (suggested to rename to AppDataStorage). Writeable by Authenticator, and readable by any application (so each app’s entry needs to be encrypted with the application’s sign keys)
  • AuthKeys data struct (to become AppTokenHashes, and referred to as such). Stored at ClientHandler nodes, and accessible by Authenticator
  • Apps themselves.
  • AuthConfig: An Authenticator only accessible location for private data.

Token Creation

Upon authorising an application the Authenticator will:

  • Create App signing key pair, AppSignKeyPair, and store this (as currently, in AuthConfig)
  • Generate and sign the application’s token with the Client SecretKey, with all permissions and caveats stored therein.
  • Pass this signed token to the App (along with apps own sign-keys)
  • Store this token, encrypted with the AppSignKeyPair in the AccessContainer. (This can be updated later if app permissions change)
  • Store a hash of the token to check its expiration, at AppTokenHashes
    // Token structure:
    #[derive(Debug, Clone, Hash, Serialize, Deserialize, PartialEq, Eq)]
    pub struct AuthToken {
        /// Auth token version
        pub version: usize,
        /// Caveats for verifying for token to be considered valid.
        pub caveats: Vec<Caveat>,
        /// Signature of the serialized token for validation against tampering.
        pub signature: Option<Signature>,
    pub type CaveatName = String;
    pub type CaveatContents = String; //| LabelCaveatContents;
    pub type Caveat = (CaveatName, CaveatContents);

This application token will hold all permissions granted (eg, balance, transfer etc, currently managed in the ClientHandler).


Caveats are arbitrary string entries to be optionally verified at various stages of the request.

A suggested implementation would be to follow macroon style verifiers .

In practice this would be check, PUT requests for example, have a valid signed token, which contains a caveat allowing PUTs.

Token Validation

At Client Handling, we need to verify:

  • That the app itself is not revoked (as current)
  • the token itself, by checking the signature against the tokens contents.
  • That the token is current, by sha-256 hashing and comparing to latest stored AppTokenHashes entry
  • All caveats of concern at that moment (ie, ClientHandling code may verify different caveats to DataHandling code, which has access to different information)

Token Invalidation

Revoking an application’s permissions, happens as currently implemented: the authenticator removes the applications auth key.

Token Deprecation

It can happen that a user adjusts an applications permission, without immediately informing the app (and issuing a new token).

As Tokens are stored for re-issue, any updated Token will be saved in the AccessContainer, and a new token hash will be stored at AppTokenHashes.

ClientHandling code will check for token deprecation by comparing passed token signature against the latest AppTokenHashes entry. A failure to match will be result in a TokenNotCurrent error.

To avoid this, Clients should automatically check for new tokens on first failed requests with refresh_access_info() in Safe Client Libs.

Token Reissue

Similar to how we currently store app sign keys, Tokens will be stored by the authenticator in the AccessContainer. Updated tokens here can be retrieved by applications via refresh_access_info() (eventually this can be automated in the app client code).

Shared Data Tokens

With Labels on the horizon, the ability to share data via validation of caveats at DataHandling is opened up.

This could be done via signing the Token with a LabelShare and indicating that the token is a shared data token, to avoid certain ClientHandler checks, and passing to DataHandling, where the labels validity can be checked against LabelPublicKeyShares on the data to determine validity.


  • The need to send all label-ids to the DataHandler could lead to increased request size.
  • Passing tokens with requests can increase request size.