Download the PHP package zeroad.network/token without Composer

On this page you can find all versions of the php package zeroad.network/token. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.

FAQ

After the download, you have to make one include require_once('vendor/autoload.php');. After that you have to import the classes with use statements.

Example:
If you use only one package a project is not needed. But if you use more then one package, without a project it is not possible to import the classes with use statements.

In general, it is recommended to use always a project to download your libraries. In an application normally there is more than one library needed.
Some PHP packages are not free to download and because of that hosted in private repositories. In this case some credentials are needed to access such packages. Please use the auth.json textarea to insert credentials, if a package is coming from a private repository. You can look here for more information.

  • Some hosting areas are not accessible by a terminal or SSH. Then it is not possible to use Composer.
  • To use Composer is sometimes complicated. Especially for beginners.
  • Composer needs much resources. Sometimes they are not available on a simple webspace.
  • If you are using private repositories you don't need to share your credentials. You can set up everything on our site and then you provide a simple download link to your team member.
  • Simplify your Composer build process. Use our own command line tool to download the vendor folder as binary. This makes your build process faster and you don't need to expose your credentials for private repositories.
Please rate this library. Is it a good library?

Informations about the package token

zeroad.network/token (PHP)

Verify Zero Ad Network subscriber tokens in your PHP backend. Offline, with no dependencies beyond ext-sodium and no calls back to us.

This is the PHP port of @zeroad.network/token. It speaks the exact same wire format, so a token minted by the platform verifies identically on either.


The thirty second version

Zero Ad Network subscribers pay a monthly fee and install a browser extension. When one of them visits your site, the extension attaches a cryptographically signed token. You verify it locally, and if it checks out you owe that visitor a clean page - no ads, no trackers, no cookie dialog, no paywall. Your share of their subscription is paid out monthly based on the time they actually spent with you.

Two headers, and this package handles both ends:

Direction Header Carries
You -> visitor Better-Web-Publisher your publisher ID, so the visit can be credited
Visitor -> you Better-Web-Token their signed, origin-bound subscription token

Requirements

Runtime Version Ready
PHP 7 7.2+
PHP 8 8.0+

ext-sodium (bundled with PHP since 7.2) is the only dependency.


Integrate

1. Register

Sign up, add your site, and copy your publisher ID (zapub_...).

2. Create a publisher, once, at startup

hostnames is every host you serve. It is required, and it matters - see why hostnames are a whitelist. Listing an apex covers its www (and vice versa), so "example.com" already admits www.example.com.

3. Wire up one middleware

Two things happen on every request: announce participation on the response, and verify the token on the request. PHP exposes the request header under a $_SERVER key, which $publisher->tokenHeaderServerKey gives you.

4. Branch on it

That is the whole integration. A working example lives in examples/.

Set Better-Web-Publisher even on pages where you never read a token. It is how the extension discovers that your site takes part at all, and how visits get attributed to you.


API

Publisher::create(array $options)

Option Type Default
publisherId string - From your dashboard. zapub_ followed by 24 alphanumerics.
hostnames string\|array - Every host you serve; an apex covers its www. Ports, schemes and paths are stripped.
publicKey string platform key Override for staging and tests. Leave alone in production.
clockToleranceSeconds int 60 Slack on expiry, for servers whose clocks drift.
cache bool\|array on See caching. false disables it.

Returns a Publisher you keep for the life of the process:

$publisher->headerName / ->headerValue "Better-Web-Publisher" and "zapub_..."
$publisher->header ["Better-Web-Publisher", "zapub_..."]
$publisher->tokenHeaderName "Better-Web-Token"
$publisher->tokenHeaderNameLowercase "better-web-token"
$publisher->tokenHeaderServerKey "HTTP_BETTER_WEB_TOKEN", the $_SERVER key
$publisher->verify($token, $hostname?) VerificationResult
$publisher->cacheStats() ["size", "maxSize", "hits", "misses", "evictions"]
$publisher->clearCache() drops every cached verdict

$publisher->verify($token, $hostname = null)

Takes the raw header value - a string, an array (some stacks hand back an array for a repeated header; the first wins), or null. Never throws on bad input; a junk token is a result, not an exception.

The hostname may be omitted when exactly one was configured. Pass the request's host when you serve several - a host outside your whitelist is rejected, never trusted.

Returns a VerificationResult. subscriber says which branch you are in:

$visitor->toArray() gives a JSON-friendly copy (with expiresAt as a unix timestamp).

The only case verify() throws is when several hostnames are configured and none is passed.

Rejection

Worth logging. Most are ordinary; two are not.

Reason (Rejection::) Means Ordinary?
MISSING No token header. Most of your traffic. yes
MALFORMED Not a well-formed token. yes
UNSUPPORTED_VERSION A newer token format. Upgrade this package. yes, but see below
EXPIRED Genuine, but past its expiry. yes
UNKNOWN_HOSTNAME The host asked for is not in your whitelist. check your config
WRONG_HOSTNAME A genuine token minted for a different site. somebody is replaying tokens
FORGED Not signed by Zero Ad Network. somebody is minting tokens

When a token arrives whose version is newer than this package understands, it is rejected as UNSUPPORTED_VERSION and a line is written to the PHP error log telling you an upgrade is due. To silence it - during a staged rollout, or in tests that feed such tokens on purpose - call ZeroAd\Token\VersionWarning::suppress() once at startup.

This package only verifies. Nothing here can mint a token - that requires a private key that never leaves the platform.


Caching

A subscriber's token stays the same all day, so a returning visitor sends bytes you have already checked. Verifying once and remembering the answer turns a pair of elliptic-curve operations into a map lookup. It is on by default and there is rarely a reason to touch it.

Option Default
enabled true
ttl 600000 milliseconds a verdict is trusted
maxSize 1000 entries (memory store only; APCu manages its own memory)
store "memory" "memory", "apcu", or "auto" - where verdicts live (see below)
prefix see below namespaces the APCu keys; ignored by the memory store

Three things it does that are worth knowing about:

Failures are cached too. A forged token costs exactly as much to reject as a real one costs to accept, and whoever sends it is likely to send it again. This is safe because, for a fixed public key, a rejection can never later become an acceptance.

A success never outlives the token. The stored expiry is the earlier of your TTL and the token's own expiresAt, so a generous TTL cannot extend anybody's subscription.

Cheap rejections are not cached. A malformed, missing or expired token is thrown out by a length or byte check. Caching those would save nothing and would hand anyone who can send a request an easy way to fill memory with distinct keys.

The default memory store is per Publisher instance and lives for the life of the PHP process. Under PHP-FPM that means per worker - it is not shared across workers, which is exactly why the publisher is created once at startup and reused, never per request. Entries are evicted least-used-first, oldest breaking ties.

Sharing verdicts across requests with APCu

On a classic PHP stack a fresh process handles each request, so the memory store starts empty every time and a returning visitor's identical token is re-verified from scratch. Point store at APCu and the verdict computed by one request is there for the next, across the whole FPM pool:

"apcu" requires the APCu extension; if it is not available the publisher logs a line and falls back to the memory store, so a token is still verified, just not shared. "auto" picks APCu when present and memory otherwise, silently - a good default for code that ships to hosts you do not control. The prefix namespaces the keys so several sites sharing one APCu segment do not collide, and clearCache() removes only keys under that prefix. With the APCu store, cacheStats() reports evictions as 0 (APCu evicts under its own memory pressure) and maxSize is advisory.


How the token works

You do not need this to integrate. You may want it before you trust it.

A token is 174 bytes, 232 base64url characters, and carries two Ed25519 signatures.

The platform signs a batch credential. Once a day, the extension generates a batch of throwaway keypairs locally and sends the public halves to us. We check the subscription is live, sign each one together with the plan and an expiry truncated to midnight UTC, and send them back. We never see the private halves, and the shared midnight expiry puts every subscriber in one anonymity set.

The extension binds one to your hostname. Offline, the first time it meets example.com it takes an unused keypair and signs your hostname with the private half, then reuses that bound token until it expires.

You verify both signatures. The first proves the platform issued the credential for a live subscription. The second proves it was minted for your host.

The hostname is deliberately absent from the wire. Your server already knows what it serves and rebuilds the signed message from that, so a token bound elsewhere simply fails the signature.

Why hostnames are a whitelist

hostnames is required, and verify() will not fall back to whatever arrived in the Host header, because tokens are bound to a hostname and Host is set by the client. Without the whitelist an attacker could bind a token to a domain they control, send it with Host: that-domain.example, and be admitted. Listing your hosts removes the possibility.

www.example.com and example.com are technically different hosts, but listing either admits both, so a site that serves both needs only one in the list. The signature is still checked against the exact host each request arrives on.


Framework examples

Laravel

Symfony

WordPress


Performance

Measured on PHP 8.5, Apple Silicon, single core, libsodium 1.0.22, via php benchmarks/verify.php:

Cold verification, end to end 88us, about 11,300/s
Cached verdict 0.95us, about 1,050,000/s
Malformed token about 1.3us, rejected on length before it is decoded

Verification is two Ed25519 checks through ext-sodium (sodium_crypto_sign_verify_detached), so the cold cost tracks libsodium and is in the same ballpark as the TypeScript SDK. At ~0.09ms it is already a rounding error next to a database query or a template render.

The one thing worth knowing is PHP's process model. The default memory cache lives only for the request that filled it, so on a classic PHP-FPM stack the cached number above applies within a request, not across them - a returning visitor's identical token is re-verified cold on the next page load. Point the cache at APCu ("cache" => ["store" => "apcu"], see Caching) to turn that repeat into a shared-memory lookup across the whole worker pool.


Troubleshooting

Every visitor comes back MISSING. Expected - only subscribers send a token. Confirm the pipe works by checking Better-Web-Publisher appears on your responses (curl -sI https://your-site).

UNKNOWN_HOSTNAME. The host being verified is not in hostnames (the www/apex sibling of a listed host counts as listed). Log $visitor->hostname to see what actually arrived; a reverse proxy may be passing something you did not expect.

WRONG_HOSTNAME from real visitors. Should be rare - www and apex are folded together. A steady stream means tokens are being replayed from another site; a trickle is usually a proxy rewriting Host.

FORGED for everybody. A publicKey override left over from staging.

Slower than expected. Check $publisher->cacheStats(). A high evictions count against size at maxSize means the working set outgrew the cache - raise maxSize. Confirm you create the publisher once at startup, never per request.


License

Apache-2.0


All versions of token with dependencies

PHP Build Version
Package Version
Requires php Version ^7.2 || ^8.0
ext-sodium Version *
Composer command for our command line client (download client) This client runs in each environment. You don't need a specific PHP version etc. The first 20 API calls are free. Standard composer command

The package zeroad.network/token contains the following files

Loading the files please wait ...