De-identified authentication without the identity overhead

Meta has open-sourced its Anonymous Credential Service (ACS), a multitenant, highly available service that lets clients authenticate without exposing user identity. The design separates authentication from data submission, so services can collect logs and telemetry without attaching personal identifiers.

The underlying protocol uses verifiable oblivious pseudorandom functions (VOPRFs) and blind signatures to split the workflow into token issuance and token redemption. During issuance, a client contacts the server over an authenticated channel, sends a blinded token, and receives a signed version back. The client unblinds it. Later, during redemption, the client sends the unblinded token along with its business data over an unauthenticated channel; the server verifies the credential and processes the request. Because the two steps are separate requests and can be separated by hours or days, an identity cannot be inferred from the data itself.

de-identified authentication anonymous credential service

The issuance step works as follows:

  1. The client generates a token and blinds it.
  2. The client sends the blinded_token plus authentication data to the server.
  3. The server signs the blinded_token and returns the signed_blinded_token.
  4. The client unblinds it to get a signed_unblinded_token.

Redemption is a single request: the client sends the original token, the signed_unblinded_token, and the business payload. The server validates the credential and, if authentic and authorized, processes the payload.

Making the protocol work at scale

The basic protocol assumes one-time redemption, a single key pair, and no adversarial servers. Production deployment requires handling several additional concerns.

Rate limiting and rotation

Credentials can be redeemed more than once if the use case permits, but only up to a limit enforced by a real-time counting service. Key rotation is equally important: because the server holds the secret key and clients hold the public key, compromised clients can be mitigated by rotating keys frequently and discarding old key material. The key management layer must coordinate rotation across the fleet according to each tenant’s cipher suites and rotation schedules. New verification keys also have to be distributed to clients before old ones are retired.

Transparency and derived keys

Rotation introduces a new risk: a malicious server could sign each client’s token with a unique key and later tie the redemption back to the individual. Key transparency solves this by letting clients see all valid public keys, so a server cannot silently issue user-specific keys.

For scalability, ACS uses key derivation functions (KDFs). Given attributes such as a use-case name or a time epoch, new secret keys and their public counterparts are derived from a single primary public key. Clients can verify derived keys without fetching new public keys, and the primary key — shipped with client code or published in a trusted location — provides a root of trust.

Production flow

With rotation, rate limiting, and transparency added, a real deployment acquires a setup step before any token is issued:

  1. The client obtains the server’s primary public key and parameters.
  2. The server derives a key pair from attributes (use case, period) and sends the public key to the client.
  3. The client validates the derived key against the primary key and attributes. If validation fails, the client can refuse, defeating any attempt to correlate keys with identities.

The subsequent issuance step adds a rate check per user before the server signs; redemption adds a redemption-count check per token before business data is processed.

Inside the ACS library

The open-source repo contains a modular C library under /lib/ with these core components:

  • VOPRF protocol: client-side blinding, unblinding and shared-secret generation for redemption; server-side signing and shared-secret generation. Two blinding versions are included.
  • Attribute-based KDF: supports rotation by deriving keys from shared values like time epochs. Recommended KDFs are Strong Diffie–Hellman Inversion (SDHI) and Naor-Reingold.
  • Discrete log proof: used twice — to verify a derived public key at setup and to verify the signed token during issuance.
  • Elliptic curves: pluggable, with Ed25519 and Ristretto provided.

Since ACS targets mobile deployments, binary size matters; libsodium is the only external dependency. A demo server and client written in C++ and built on Apache Thrift 0.16 are included under /demo/.

A practical example

Consider a weather service that only serves authenticated users. The naive design forwards authentication_data and report_data together on every request:

# client
get_report(authentication_data)
# server
if check_authentication(request.authentication_data):
    response.report = report_data

ACS splits those two payloads into separate channels:

# client - authentication
token = random_string()
blinded_token, blinding_factor = blind(token)
signed_blinded_token = request_token_from_server(authentication_data, blinded_token)
signed_unblinded_token = unblind(signed_blinded_token, blinding_factor)
# client - get data
client_secret = client_finalize(token, signed_unblinded_token)
get_report(token, client_secret)
# token issuance server
if check_authentication(request.authentication_data):
    signed_blinded_token = evaluate(blinded_token)
    response.signed_blinded_token = signed_blinded_token
# token redemption server
server_secret = server_finalize(request.token)
if server_secret == request.client_secret:
    response.report = report_data

The client first authenticates and requests data, then blinds a token and submits it for signing. After the server’s authentication check, the client unblinds the signed token and verifies it with the public key and proof. The client then redeems the token with the requested report data; the server validates and runs business logic, or rejects the request.

Key rotation adds two preliminary steps: the client fetches the primary public key, then fetches a derived public key for the current attributes. This establishes key transparency before any token is generated. The full stage is:

# client - setup
primary_public_key = request_primary_public_key_from_server()
# client - authentication
public_key, pk_proof = get_public_key_from_server(attribute)
if !dleqproof_verify(public_key, pk_proof, primary_public_key, attribute):
    raise Exception("malicious server!")
token = random_string()
unblinded_token, blinding_factor = blind(token)
signed_blinded_token, proof = request_token_from_server(authentication_data, blinded_token)
signed_unblinded_token = verifiable_unblind(signed_blinded_token, blinding_factor, proof, public_key)

This design blocks a malicious server from using rotation to segregate users. It is production-ready as a prototype, though client-side token storage and server-side rate limiting are not part of the open-source repo — those remain deployment-specific concerns.

Roadmap

The ACS project expects to adopt the IETF VOPRF draft standard, and a libsodium-free light version is planned for environments with strict binary size constraints. Contribution guidelines and code are available in the ACS GitHub repository.