ptondereau / biscuit-symfony-bundle
Symfony bundle for Biscuit authorization tokens
Package info
github.com/ptondereau/biscuit-sf-bundle
Type:symfony-bundle
pkg:composer/ptondereau/biscuit-symfony-bundle
Requires
- php: >=8.1
- ext-biscuit_php: *
- symfony/config: ^6.4 || ^7.0 || ^8.0
- symfony/console: ^6.4 || ^7.0 || ^8.0
- symfony/dependency-injection: ^6.4 || ^7.0 || ^8.0
- symfony/framework-bundle: ^6.4 || ^7.0 || ^8.0
- symfony/security-bundle: ^6.4 || ^7.0 || ^8.0
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.64
- phpstan/phpstan: ^2.0
- phpstan/phpstan-phpunit: ^2.0
- phpstan/phpstan-symfony: ^2.0
- phpunit/phpunit: ^10.0 || ^11.0
- symfony/cache: ^6.4 || ^7.0 || ^8.0
- symfony/maker-bundle: ^1.50
- symfony/web-profiler-bundle: ^6.4 || ^7.0 || ^8.0
- symfony/yaml: ^6.4 || ^7.0 || ^8.0
This package is auto-updated.
Last update: 2026-05-06 14:49:50 UTC
README
Symfony bundle for Biscuit authorization tokens.
About
Biscuit is a bearer token format with offline attenuation, third-party blocks, and a Datalog-based authorization language. This bundle integrates Biscuit into Symfony's Security component so you can authenticate requests carrying Biscuit tokens and enforce policies through the standard #[IsGranted] attribute.
What you get:
- Token extraction from
Authorizationheader and/or cookies - Symfony authenticator that validates the token's signature against your public key
- A
BiscuitVoterthat runs your Datalog policies against the request, fully driven by#[IsGranted] - Configurable token caching and revocation checking
- A web profiler panel showing the current token, its blocks, and every policy decision
- Console commands to generate keys, mint tokens from templates, and inspect tokens
- A
make:biscuit-policymaker - Test helpers to mint tokens and authenticate functional tests
Read the Datalog reference at biscuitsec.org/docs/reference/datalog.
Requirements
- PHP 8.1 or higher
- Symfony 6.4, 7.4, or 8.0
- The
biscuit-phpPHP extension (version 0.4.0)
Installation
Install the PHP extension via pie:
pie install ptondereau/biscuit-php:0.4.0
Install the bundle via Composer:
composer require ptondereau/biscuit-symfony-bundle
If you are not using Symfony Flex, register the bundle manually in config/bundles.php:
return [ // ... Biscuit\BiscuitBundle\BiscuitBundle::class => ['all' => true], ];
Quick Start
Generate a key pair:
bin/console biscuit:keys:generate
Configure the bundle (config/packages/biscuit.yaml):
biscuit: keys: public_key: '%env(BISCUIT_PUBLIC_KEY)%' private_key: '%env(BISCUIT_PRIVATE_KEY)%' policies: admin_only: 'allow if role("admin")'
Wire the authenticator into your firewall (config/packages/security.yaml):
security: firewalls: api: pattern: ^/api stateless: true custom_authenticators: - biscuit.authenticator
Protect a controller with #[IsGranted]:
use Symfony\Component\Routing\Attribute\Route; use Symfony\Component\Security\Http\Attribute\IsGranted; final class AdminController { #[Route('/api/admin', methods: ['GET'])] #[IsGranted('admin_only')] public function index(): Response { return new JsonResponse(['ok' => true]); } }
That's it. Requests without a valid token get 401, requests whose token does not satisfy the policy get 403.
Configuration Reference
biscuit: keys: public_key: ~ # Public key in hex private_key: ~ # Private key in hex public_key_file: ~ # Path to public key file (alternative to public_key) private_key_file: ~ # Path to private key file (alternative to private_key) algorithm: ed25519 # ed25519 or secp256r1 security: token_extractor: header: true # Extract token from Authorization header cookie: false # Cookie name to read from, or false to disable cache: enabled: false # Enable token verification caching pool: cache.app # Cache pool service ID ttl: 3600 # Cache TTL in seconds revocation: enabled: false # Enable token revocation checking service: ~ # Revocation checker service ID policies: # Named policies referenced by #[IsGranted] admin_only: 'allow if role("admin")' scope_read: 'allow if scope({resource}, "read")' token_templates: # Templates used by BiscuitTokenFactory and biscuit:token:create admin_token: facts: - 'user({id})' - 'role("admin")' checks: - 'check if time($t), $t < {expiry}' rules: []
Key Management
The KeyManager service exposes the configured key pair. Three input forms are supported:
- Inline hex via
keys.public_keyandkeys.private_key(recommended via env vars). - Files via
keys.public_key_fileandkeys.private_key_file. - A new pair generated on demand if none is configured (only useful in tests).
To generate a fresh pair from the CLI:
bin/console biscuit:keys:generate --algorithm=ed25519
The command prints the hex-encoded keys to stdout. Store them in your secrets manager and inject them via environment variables.
Token Extraction
Tokens are extracted by the ChainTokenExtractor, which delegates to one or more named extractors in order:
HeaderTokenExtractorreadsAuthorization: Bearer <token>from the request.CookieTokenExtractorreads a configurable cookie name.
Enable both:
biscuit: security: token_extractor: header: true cookie: biscuit_token
The chain stops at the first extractor that returns a non-null value. To add a custom extractor, implement Biscuit\BiscuitBundle\Token\Extractor\TokenExtractorInterface and register it as a service tagged into the chain.
Authentication
The bundle ships a single authenticator: BiscuitAuthenticator. Add it as a custom_authenticators entry on any stateless firewall that should accept Biscuit tokens.
A successful authentication produces a BiscuitUser whose getBiscuit() method returns the verified token, which is then available throughout the request. Failed authentication throws RevokedTokenException (when revocation is enabled and the token is on the revocation list) or returns a generic 401 for invalid signatures, malformed tokens, and missing extractors.
The BiscuitBadge is attached to the security passport so downstream voters and listeners can detect Biscuit-authenticated requests.
Authorization
Policies are referenced by name from #[IsGranted]:
biscuit: policies: admin_only: 'allow if role("admin")' owner_only: 'allow if user({user_id})' scope_read: 'allow if scope({resource}, "read")'
#[IsGranted('admin_only')] public function dashboard(): Response { /* ... */ } #[IsGranted('owner_only', subject: ['user_id' => $userId])] public function profile(int $userId): Response { /* ... */ } #[IsGranted('scope_read', subject: $resource)] public function show(string $resource): Response { /* ... */ }
The voter resolves the policy and runs an authorizer over the verified token. The subject: argument is bound into the policy as parameters:
- A string subject becomes
{resource}in the policy. - An object with
getId()becomes{resource}as the string-cast id. - An associative array is bound key-by-key (use this for multi-parameter policies).
You can also pass a Datalog string directly to #[IsGranted] for ad-hoc policies that aren't worth naming:
#[IsGranted('allow if scope({resource}, "read")', subject: $resource)]
If the configured policies do not match, the voter abstains and falls back to other voters.
Token Templates
Define reusable token shapes in configuration:
biscuit: token_templates: admin_token: facts: - 'user({id})' - 'role("admin")' scoped_reader: facts: - 'user({id})' - 'scope({resource}, "read")'
Mint tokens from templates with BiscuitTokenFactory:
use Biscuit\BiscuitBundle\Token\BiscuitTokenFactory; final class IssueTokenAction { public function __construct(private readonly BiscuitTokenFactory $factory) {} public function __invoke(int $userId, string $dog): string { $token = $this->factory->create('scoped_reader', [ 'id' => $userId, 'resource' => $dog, ]); return $token->toBase64(); } }
Console Commands
| Command | Purpose |
|---|---|
biscuit:keys:generate |
Generate an ed25519 or secp256r1 key pair |
biscuit:token:create |
Mint a token from a configured template |
biscuit:token:inspect |
Decode and pretty-print a Biscuit token |
biscuit:policy:test |
Run a configured policy against a token |
Each command exposes --help for the full option list.
Maker
If you also have symfony/maker-bundle installed:
bin/console make:biscuit-policy ArticleViewerPolicy
This generates src/Security/Policy/ArticleViewerPolicy.php with a documented skeleton including a NAME constant, a POLICY Datalog string, and a usage example with #[IsGranted].
Token Caching
For high-throughput APIs you can cache successful token verifications to avoid re-running signature checks on every request:
biscuit: cache: enabled: true pool: cache.app ttl: 600
Cache keys are derived from the serialized token. Failed verifications are never cached.
Token Revocation
To enforce a revocation list, implement Biscuit\BiscuitBundle\Cache\Revocation\RevocationCheckerInterface and wire it in:
biscuit: revocation: enabled: true service: App\Security\MyRevocationChecker
A reference implementation backed by a Symfony cache pool is provided as Biscuit\BiscuitBundle\Cache\Revocation\CacheRevocationChecker.
When revocation is enabled, the authenticator throws RevokedTokenException for any token whose revocation IDs intersect the revoked set. Revocation checks happen after signature verification but before policy evaluation.
Web Profiler Integration
When symfony/web-profiler-bundle is installed in the dev environment, the bundle adds a Biscuit panel showing:
- Whether a token was attached to the request, with block count and revocation IDs
- All blocks in the token (Datalog source)
- Every policy check performed during the request, with parameters and pass/fail outcome
The toolbar shows a green/red indicator and the count of policy checks.
Testing Helpers
The Biscuit\BiscuitBundle\Test namespace provides utilities for functional tests.
BiscuitTestTrait mints tokens against a per-test-class key pair without needing to mock anything:
use Biscuit\BiscuitBundle\Test\BiscuitTestTrait; use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; final class ProtectedEndpointTest extends WebTestCase { use BiscuitTestTrait; public function testAdminCanAccess(): void { $token = $this->createTestTokenBase64('user(1); role("admin")'); $client = static::createClient(); $client->request('GET', '/api/admin', server: [ 'HTTP_AUTHORIZATION' => 'Bearer ' . $token, ]); self::assertResponseIsSuccessful(); } }
TestBiscuitAuthenticator is a drop-in replacement for the production authenticator that trusts tokens signed by the test key pair.
BiscuitFixtures and BiscuitFixtureLoader load Datalog scenarios from YAML files for repeatable fixture data.
Development
Run the full quality gate locally:
composer check
This runs php-cs-fixer (dry run), phpstan at level 8, and the PHPUnit suite.
To auto-fix style issues:
composer cs-fix
Contributing
See CONTRIBUTING.md. All contributors are expected to follow the Code of Conduct.
Security
Vulnerabilities should be reported to security@biscuitsec.org. See SECURITY.md for details.
License
Apache License 2.0. See LICENSE.