Search by

mazedlx / laravel-feature-policy

mazedlx

Add Feature-Policy headers to the responses of a Laravel app

Package info

github.com/mazedlx/laravel-feature-policy

pkg:composer/mazedlx/laravel-feature-policy

Statistics

Installs: 248 314

Dependents: 0

Suggesters: 0

Stars: 17

Open Issues: 0

v3.3.0 2026-09-29 16:59 UTC

README

Latest Version on Packagist Tests Analyse and format Total Downloads

The Permissions-Policy, which previously was known as the Feature-Policy. But since it came out of draft, it was renamed to "Permissions-Policy".
The "Permissions-Policy" is an HTTP header which can be used to restrict the abilities of a browser.

Where the Content-Security-Policy focuses on security, the "Permissions-Policy" focuses on allowing or disabling the abilities of the browser.
This can be done though the HTTP header, which this package focuses on, but it can also do this through the allows attribute on the iframe element.

iframe example
<iframe width="643" height="360" frameborder="0" allow="autoplay; fullscreen" allowfullscreen></iframe>

More on the header itself can be found on the following sites.

Installation

Requires Laravel 12 or 13, and PHP 8.2+. Laravel 10 users should use the 2.x series, Laravel < 10 users should stick to v1.3.

The package can be installed though composer:

$ composer require mazedlx/laravel-feature-policy

After which the config file needs to be published:

$ php artisan vendor:publish --provider="Mazedlx\FeaturePolicy\FeaturePolicyServiceProvider" --tag="config"

Which looks like this:

Config file
<?php

return [
    /*
     * "Permissions-Policy" headers will only be added if this is set to true
     */
    'enabled' => env('FPH_ENABLED', true),

    /*
     * See "Deprecation Notices" below.
     */
    'deprecations' => [
        'enabled' => env('FPH_DEPRECATION_NOTICES') ?? false,
    ],

    /*
     * A policy will determine which "Permissions-Policy" headers will be set.
     * A valid policy extends `Mazedlx\FeaturePolicy\Policies\Policy`
     */
    'policy' => Mazedlx\FeaturePolicy\Policies\Basic::class,

    /** @see https://github.com/w3c/webappsec-permissions-policy/blob/main/features.md */
    'directives' => [
        // enable proposed, not-yet-standardized directives
        'proposal' => env('FPH_PROPOSAL_ENABLED', false),
        // enable experimental directives
        'experimental' => env('FPH_EXPERIMENTAL_ENABLED', false),
    ],

    'reporting' => [
        'enabled' => env('FPH_REPORTING_ENABLED', false),
        'report_only' => env('FPH_REPORT_ONLY', false),
        'url' => env('FPH_REPORTING_URL', 'https://reportingapi.tools/public/submit'),
    ],
];

Middleware

You can add "Permissions-Policy" headers to all responses by registering Mazedlx\FeaturePolicy\AddFeaturePolicyHeaders::class as web middleware in bootstrap/app.php:

Middleware example
// bootstrap/app.php

use Illuminate\Foundation\Configuration\Middleware;

->withMiddleware(function (Middleware $middleware) {
    $middleware->web(append: [
        \Mazedlx\FeaturePolicy\AddFeaturePolicyHeaders::class,
    ]);
})

If your app still uses the older app/Http/Kernel.php structure, register it in $middlewareGroups there instead:

// app/Http/Kernel.php

protected $middlewareGroups = [
    'web' => [
        ...
        \Mazedlx\FeaturePolicy\AddFeaturePolicyHeaders::class,
    ]
];

Alternatively you can add the middleware to a single route and route group:

Route example
// in a routes file
use App\Http\Controllers\HomeController;
use Mazedlx\FeaturePolicy\AddFeaturePolicyHeaders;

Route::get('/home', HomeController::class)
    ->middleware(AddFeaturePolicyHeaders::class);

You could even pass a policy as a parameter and override the policy specified in the config file:

// in a routes file
use App\Http\Controllers\HomeController;
use Mazedlx\FeaturePolicy\AddFeaturePolicyHeaders;

Route::get('/home', HomeController::class)
    ->middleware(AddFeaturePolicyHeaders::class . ':' . MyFeaturePolicy::class);

Usage

This package allows you to configure the policies that end up in the "Permissions-Policy" header.

This policy determines which directives will be set in the "Permissions-Policy" header of the response.

It uses the following syntax;

Permissions-Policy: <directive> <allowlist>

An example of a "Permissions-Policy" directive is microphone:

Permissions-Policy: microphone=(self "https://spatie.be")

In the above example by specifying microphone and allowing it for self makes the permission disabled for all origins except our own and https://spatie.be.

The current list of directives can be found here. Some of these are:

  • accelerometer
  • ambient-light-sensor
  • autoplay
  • camera
  • encrypted-media
  • fullscreen
  • geolocation
  • gyroscope
  • magnetometer
  • microphone
  • midi
  • payment
  • picture-in-picture
  • screen-wake-lock
  • usb
  • xr-spatial-tracking

You can add multiple policy options as an array or as a single string with space-separated options:

// in a policy
...
    ->addDirective(Directive::CAMERA, [
        Value::SELF,
        'spatie.be',
    ])
    ->addDirective(Directive::GYROSCOPE, 'self spatie.be')
...

Creating Policies

The policy key of the feature-policy config file is set to Mazedlx\FeaturePolicy\Policies\Basic::class by default, which allows your site to use a few of the available features. The class looks like this:

Basic policy
<?php

namespace Mazedlx\FeaturePolicy\Policies;

use Mazedlx\FeaturePolicy\Value;
use Mazedlx\FeaturePolicy\Directive;

final class Basic extends Policy
{
    public function configure()
    {
        $this->addDirective(Directive::GEOLOCATION, Value::SELF)
            ->addDirective(Directive::FULLSCREEN, Value::SELF);
    }
}

Let's say you're happy with allowing geolocation and fullscreen but also wanted to add www.awesomesite.com to gain access to this feature, then you can easily extend the class:

MyFeature policy
<?php

namespace App\Services\FeaturePolicy\Policies;

use Mazedlx\FeaturePolicy\Directive;
use Mazedlx\FeaturePolicy\Policies\Basic;

class MyFeaturePolicy extends Basic
{
    public function configure()
    {
        parent::configure();

        $this->addDirective(Directive::GEOLOCATION, 'www.awesomesite.com')
            ->addDirective(Directive::FULLSCREEN, 'www.awesomesite.com');
    }
}

Don't forget to change the policy key in the feature-policy config file to the class name fo your policy (e.g. App\Services\Policies\MyFeaturePolicy).

Deprecation Notices

Some directives get renamed, retired, or dropped from the spec over time (speaker was removed entirely, vr was renamed to xr-spatial-tracking, and so on). Rather than silently keep sending a directive that no longer does anything, this package can surface that to API consumers/tooling through standard Link response headers.

This is opt-in, via the deprecations.enabled config key (FPH_DEPRECATION_NOTICES env var, defaults to false):

'deprecations' => [
    'enabled' => env('FPH_DEPRECATION_NOTICES') ?? false,
],

When enabled, every deprecated directive present in the resolved policy gets its own Link header (RFC 8288), instead of one bespoke combined header, so it's safe to have several deprecated directives active at once, each with its own target, reason, and date:

Link: <https://github.com/w3c/webappsec-permissions-policy/pull/360>; rel="deprecation"; feature="speaker"; title="Removed from the spec; audio output device access is no longer a Permissions-Policy directive"; since="2020-01-15"
  • rel="deprecation" marks it as a deprecation notice, filterable independently of any other Link headers a response might carry.
  • feature is the directive name.
  • title is a human-readable explanation of what happened.
  • since is the date the directive was actually deprecated upstream (not when this package happened to notice), sourced from a primary reference: the removing spec PR, a browser vendor's own deprecation announcement, etc.
  • The link target itself points at that same primary source.

If you're writing a custom directive and want it to participate in this, implement Mazedlx\FeaturePolicy\FeatureGroups\DeprecatedDirective on top of the usual DirectiveContract methods:

use Mazedlx\FeaturePolicy\FeatureGroups\DeprecatedDirective;

final class MyOldDirective extends Directive implements DeprecatedDirective
{
    // ...name(), specificationName(), specificationUrl(), browserSupport(), browserSupportUrl()

    public function deprecatedSince(): DateTimeImmutable
    {
        return new DateTimeImmutable('2024-01-01');
    }
}

A directive is only considered deprecated if it implements this interface; there's no separate boolean flag to keep in sync.

Testing

You can run all tests with:

$ composer test

Changelog

Please see CHANGELOG for more information what has changed recently.

Contributing

Please see CONTRIBUTING for details.

Contributers

Made with contrib.rocks.

Security

If you discover any security related issues please email mazedlx@gmail.com instead of using the issue tracker.

Credits

This package is strongly inspired by Spatie laravel-csp package. Thanks to Freek van der Herten and Thomas Verhelst for creating such an awesome package and doing all the heavy lifting!

Support

If you like this package please feel free to star it.

License

The MIT License (MIT). Please see LICENSE for more information.