ptondereau / biscuit-php
PHP bindings for Biscuit authorization tokens
Package info
github.com/ptondereau/biscuit-php
Type:php-ext
Ext name:ext-biscuit_php
pkg:composer/ptondereau/biscuit-php
Requires
- php: ^8.1
Requires (Dev)
- carthage-software/mago: 1.25.2
- phpunit/phpunit: ^10
README
PHP bindings for Biscuit, a bearer token supporting offline attenuation, decentralized verification, and powerful authorization policies.
Documentation and Specifications
- Biscuit Website - Documentation and examples
- Biscuit Specification
- Biscuit Rust - Technical details
- Upgrading Guide - Migration guide for breaking changes
Requirements
cargo-php- PHP >= 8.1 with
php-devinstalled - Rust
- Clang
Installation
Pre-built Binaries (Recommended)
Pre-built binaries are available for Linux (glibc/musl, x86_64/arm64), macOS (x86_64/arm64), and Windows (x86_64) across PHP 8.1 through 8.5, with both Thread-Safe (TS) and Non-Thread-Safe (NTS) variants. Download the appropriate archive for your platform from the latest release.
Quick Installation (Linux glibc x86_64, PHP 8.3 NTS shown)
# Pick the archive matching your PHP version, libc, arch, and TS/NTS: VERSION=v0.4.0 wget https://github.com/ptondereau/biscuit-php/releases/download/${VERSION}/php_biscuit_php-${VERSION}_php8.3-x86_64-linux-glibc-nts.zip unzip php_biscuit_php-${VERSION}_php8.3-x86_64-linux-glibc-nts.zip # Move to PHP extension directory (adjust path for your system) sudo mv biscuit_php.so /usr/lib/php/$(php -r 'echo PHP_MAJOR_VERSION.".".PHP_MINOR_VERSION;')/ # Enable the extension echo "extension=biscuit_php.so" | sudo tee /etc/php/$(php -r 'echo PHP_MAJOR_VERSION.".".PHP_MINOR_VERSION;')/mods-available/biscuit_php.ini sudo phpenmod biscuit_php # Verify installation php -m | grep biscuit_php
PIE installation
We support PIE installation:
pie install ptondereau/biscuit-php
and you can add in your composer.json:
{
// ...
"ext-biscuit_php": "*",
// ...
}
Build from Source
If pre-built binaries are not available for your platform:
# Clone the repository git clone https://github.com/ptondereau/biscuit-php.git cd biscuit-php # Install dependencies composer install # Build the extension cargo build --release # Load the extension php -dextension=target/release/libbiscuit_php.so -m | grep biscuit_php
IDE stubs and static analysis
PHP stubs for the whole extension API are published as a dedicated Composer package, ptondereau/biscuit-php-stubs:
composer require --dev ptondereau/biscuit-php-stubs
The stubs ship with full docblocks (usage examples, precise types, and @throws annotations), so IDEs, PHPStan, and Psalm can understand the API without loading the extension. Every stub method throws an Error when called, so a missing extension fails loudly at runtime instead of silently.
Quick Start
<?php use Biscuit\Auth\{Biscuit, BiscuitBuilder, KeyPair, AuthorizerBuilder}; // Generate a keypair $root = new KeyPair(); // Create a biscuit token (can pass code directly to constructor) $builder = new BiscuitBuilder('user("alice"); resource("file1")'); $biscuit = $builder->build($root->getPrivateKey()); // Serialize to base64 $token = $biscuit->toBase64(); // Parse and authorize $parsed = Biscuit::fromBase64($token, $root->getPublicKey()); // Authorizer with inline code $authBuilder = new AuthorizerBuilder('allow if user("alice"), resource("file1")'); $authorizer = $authBuilder->build($parsed); // Check authorization: returns the matched allow policy, // or throws Biscuit\Exception\AuthorizationException on failure $policy = $authorizer->authorize(); echo "Authorized by policy #{$policy->getPolicyId()} ({$policy->getKind()})";
Advanced Examples
Third-Party Blocks
// Create biscuit $biscuit = $builder->build($rootKey); // Third-party attestation $thirdPartyKey = new KeyPair(); $request = $biscuit->thirdPartyRequest(); $externalBlock = new BlockBuilder(); $externalBlock->addCode('external_fact("verified");'); $signedBlock = $request->createBlock($thirdPartyKey->getPrivateKey(), $externalBlock); $biscuitWithAttestation = $biscuit->appendThirdParty( $thirdPartyKey->getPublicKey(), $signedBlock );
Parameterized Primitives
use Biscuit\Auth\{Fact, Rule, Check, Policy}; // Fact with constructor params $fact = new Fact('user({id})', ['id' => 'alice']); // Rule with params $rule = new Rule('can_read($u, {res}) <- user($u)', ['res' => 'file1']); // Check with params $check = new Check('check if user({name})', ['name' => 'alice']); // Policy with params $policy = new Policy('allow if user({name})', ['name' => 'alice']); // Or use set() method for dynamic values $fact = new Fact('user({id})'); $fact->set('id', $userId);
Authorizer Queries
$authorizer = $authBuilder->build($biscuit); $rule = new Rule('users($id) <- user($id)'); $facts = $authorizer->query($rule); foreach ($facts as $fact) { echo "Found: {$fact->name()}\n"; }
Snapshot Persistence
// Save authorizer state $snapshot = $authorizer->base64Snapshot(); // Restore later $restored = Authorizer::fromBase64Snapshot($snapshot); $policy = $restored->authorize();
PEM Key Import
$pem = "-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----"; $privateKey = PrivateKey::fromPem($pem); $keyPair = KeyPair::fromPrivateKey($privateKey);
Direct Private Key Generation
use Biscuit\Auth\{PrivateKey, Algorithm}; // Generate a random private key (Ed25519 by default) $privateKey = PrivateKey::generate(); // Generate with specific algorithm $privateKey = PrivateKey::generate(Algorithm::Secp256r1); // Get the corresponding public key $publicKey = $privateKey->getPublicKey();
Algorithm Support
use Biscuit\Auth\Algorithm; // Ed25519 is the default algorithm (recommended) $keypair1 = new KeyPair(); // Uses Ed25519 // Explicitly use Secp256r1 $keypair2 = new KeyPair(Algorithm::Secp256r1); // Key import defaults to Ed25519 $publicKey = PublicKey::fromBytes($bytes); // Defaults to Ed25519 $publicKey = PublicKey::fromBytes($bytes, Algorithm::Ed25519); // Explicit Ed25519 $publicKey = PublicKey::fromBytes($bytes, Algorithm::Secp256r1); // Explicit Secp256r1
Testing
cargo build
php \
-dextension=target/debug/libbiscuit_php.so \
vendor/bin/phpunit
Formatting
We're using Mago as code-style formatter for PHP code
composer install
cargo build
php \
-dextension=target/debug/libbiscuit_php.so \
vendor/bin/mago lint // and format
PHP Stubs
The stubs live in stubs/ and are maintained by hand, one file per class. On every push to main and on release tags, the sync-stubs workflow mirrors them (together with dist/stubs-package/) to ptondereau/biscuit-php-stubs, which is what Packagist tracks. Release tags are replicated on the mirror so stub versions follow extension releases.
When changing the PHP API exposed from Rust, update the matching stub in stubs/ in the same pull request.
Contributing
Contributions are welcome! Please:
- Add tests for new features
- Update documentation and the PHP stubs in
stubs/when the API changes - Ensure all tests pass
License
Licensed under Apache License, Version 2.0.