Getting started with Trust Tokens

Trust Tokens is a new API to enable a website to convey a limited amount of information from one browsing context to another (for example, across sites) to help combat fraud, without passive tracking.



Summary

Trust tokens enable an origin to issue cryptographic tokens to a user it trusts. The tokens are stored by the user's browser. The browser can then use the tokens in other contexts to evaluate the user's authenticity.

The Trust Token API enables trust of a user in one context to be conveyed to another context without identifying the user or linking the two identities.

You can try out the API with our demo, and inspect tokens in the Chrome DevTools Network and Application tabs.

Screenshot showing Trust Tokens in the Chrome DevTools Network tab.
Trust Tokens in the Chrome DevTools Network tab.
Screenshot showing Trust Tokens in the Chrome DevTools Application tab.
Trust Tokens in the Chrome DevTools Application tab.

Why do we need Trust Tokens?

The web needs ways to establish trust signals which show that a user is who they say they are, and not a bot pretending to be a human, or a malicious third-party defrauding a real person or service. Fraud protection is particularly important for advertisers, ad providers, and CDNs.

Unfortunately, many existing mechanisms to gauge and propagate trustworthiness—to work out if an interaction with a site is from a real human, for example—take advantage of techniques that can also be used for fingerprinting.

The API must preserve privacy, enabling trust to be propagated across sites without individual user tracking.

What's in the Trust Tokens proposal?

The web relies on building trust signals to detect fraud and spamming. One way to do this is by tracking browsing with global, cross-site per-user identifiers. For a privacy-preserving API, that's not acceptable.

From the proposal explainer:

This API proposes a new per-origin storage area for "Privacy Pass" style cryptographic tokens, which are accessible in third party contexts. These tokens are non-personalized and cannot be used to track users, but are cryptographically signed so they cannot be forged.

When an origin is in a context where they trust the user, they can issue the browser a batch of tokens, which can be "spent" at a later time in a context where the user would otherwise be unknown or less trusted. Crucially, the tokens are indistinguishable from one another, preventing websites from tracking users through them.

We further propose an extension mechanism for the browser to sign outgoing requests with keys bound to a particular token redemption.

Sample API usage

The following is adapted from sample code in the API explainer.

Imagine that a user visits a news website (publisher.example) which embeds advertising from a third party ad network (foo.example). The user has previously used a social media site that issues trust tokens (issuer.example).

The sequence below shows how trust tokens work.

1. The user visits issuer.example and performs actions that lead the site to believe they are a real human, such as account activity, or passing a CAPTCHA challenge.

2. issuer.example verifies the user is a human, and runs the following JavaScript to issue a trust token to the user's browser:

fetch('https://issuer.example/trust-token', {
  trustToken: {
    type: 'token-request',
    issuer: 'https://issuer.example'
  }
}).then(...)

3. The user's browser stores the trust token, associating it with issuer.example.

4. Some time later, the user visits publisher.example.

5. publisher.example wants to know if the user is a real human. publisher.example trusts issuer.example, so they check if the user's browser has valid tokens from that origin:

document.hasTrustToken('https://issuer.example');

6. If this returns a promise that resolves to true, that means the user has tokens from issuer.example, so publisher.example can attempt to redeem a token:

fetch('https://issuer.example/trust-token', {
trustToken: {
  type: 'token-redemption',
  issuer: 'https://issuer.example',
  refreshPolicy: {none, refresh}
}
}).then(...)

With this code:

  1. The redeemer publisher.example requests a redemption.
  2. If the redemption is successful, the issuer issuer.example returns a redemption record which indicates that at some point they issued a valid token to this browser.

    7. Once the promise returned by fetch() has resolved, the redemption record can be used in subsequent resource requests:

fetch('https://foo.example/get-content', {
  trustToken: {
    type: 'send-redemption-record',
    issuers: ['https://issuer.example', ...]
  }
});

With this code:

  1. Redemption records are included as a request header Sec-Redemption-Record.
  2. foo.example receives the redemption record and can parse the record to determine whether issuer.example thought this user was a human.
  3. foo.example responds accordingly.
How can a website work out whether to trust you?

You might have shopping history with an e-commerce site, check-ins on a location platform, or account history at a bank. Issuers might also look at other factors such as how long you've had an account, or other interactions (such as CAPTCHAs or form submission) that increase the issuer's trust in the likelihood that you're a real human.

Trust token issuance

If the user is deemed to be trustworthy by a trust token issuer such as issuer.example, the issuer can fetch trust tokens for the user by making a fetch() request with a trustToken parameter:

fetch('issuer.example/trust-token', {
  trustToken: {
    type: 'token-request'
  }
}).then(...)

This invokes an extension of the Privacy Pass issuance protocol using a new cryptographic primitive:

  1. Generate a set of pseudo-random numbers known as nonces.

  2. Blind the nonces (encode them so the issuer can't view their contents) and attach them to the request in a Sec-Trust-Token header.

  3. Send a POST request to the endpoint provided.

The endpoint responds with blinded tokens (signatures on the blind nonces), then the tokens are unblinded and stored internally together with the associated nonces by the browser as trust tokens.

Trust token redemption

A publisher site (such as publisher.example in the example above) can check if there are trust tokens available for the user:

const userHasTokens = await document.hasTrustToken('issuer.example/trust-token');

If there are tokens available, the publisher site can redeem them to get a redemption record:

fetch('issuer.example/trust-token', {
  ...
  trustToken: {
    type: 'token-redemption',
    refreshPolicy: 'none'
  }
  ...
}).then(...)

The publisher can include redemption records in requests that require a trust token—such as posting a comment, liking a page, or voting in a poll—by using a fetch() call like the following:

fetch('https://foo.example/post-comment', {
  ...
  trustToken: {
    type: 'send-redemption-record',
    issuers: ['issuer.example/trust-token', ...]
  }
  ...
}).then(...);

Redemption records are included as a Sec-Redemption-Record request header.

Privacy considerations

Tokens are designed to be 'unlinkable'. An issuer can learn aggregate information about which sites its users visit, but can't link issuance with redemption: when a user redeems a token, the issuer can't tell the token apart from other tokens it has created. However, trust tokens currently do not exist in a vacuum: there are other ways an issuer could currently—in theory—join a user's identity across sites, such as third-party cookies and covert tracking techniques. It is important for sites to understand this ecosystem transition as they plan their support. This is a general aspect of the transition for many Privacy Sandbox APIs, so not discussed further here.

Security considerations

Trust token exhaustion: a malicious site could deliberately deplete a user's supply of tokens from a particular issuer. There are several mitigations against this kind of attack, such as enabling issuers to provide many tokens at once, so users have an adequate supply of ensuring browsers only ever redeem one token per top-level page view.

Double-spend prevention: malware might attempt to access all of a user's trust tokens. However, tokens will run out over time, since every redemption is sent to the same token issuer, which can verify that each token is used only once. To mitigate risk, issuers could also sign fewer tokens.

Request mechanisms

It might be possible to allow for sending redemption records outside of fetch(), for example with navigation requests. Sites might also be able to include issuer data in HTTP response headers to enable token redemption in parallel with page loading.

To reiterate: this proposal needs your feedback! If you have comments, please create an issue on the Trust Token explainer repository.

Find out more


Thanks to all those who helped write and review this post.

Photo by ZSun Fu on Unsplash.