├── .github ├── FUNDING.yml ├── dependabot.yml ├── ISSUE_TEMPLATE │ └── config.yml └── workflows │ ├── fix-php-code-style-issues.yml │ ├── update-changelog.yml │ └── run-tests.yml ├── src ├── Facades │ └── UrlSigner.php ├── UrlSigner.php ├── Middleware │ └── ValidateSignature.php ├── UrlSignerServiceProvider.php └── Commands │ └── GenerateUrlSignerSignatureKey.php ├── .editorconfig ├── UPGRADING.md ├── config └── url-signer.php ├── phpunit.xml.dist ├── LICENSE.md ├── composer.json ├── CHANGELOG.md └── README.md /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: spatie 2 | -------------------------------------------------------------------------------- /src/Facades/UrlSigner.php: -------------------------------------------------------------------------------- 1 | validate($request->fullUrl()); 12 | 13 | if (! $urlHasValidSignature) { 14 | $this->handleUnsignedUrl($request); 15 | } 16 | 17 | return $next($request); 18 | } 19 | 20 | protected function handleUnsignedUrl($request) 21 | { 22 | abort(403); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /config/url-signer.php: -------------------------------------------------------------------------------- 1 | env('URL_SIGNER_SIGNATURE_KEY'), 9 | 10 | /* 11 | * The default expiration time of a URL in seconds. 12 | */ 13 | 'default_expiration_time_in_seconds' => 60 * 60 * 24, 14 | 15 | /* 16 | * These strings are used a parameter names in a signed url. 17 | */ 18 | 'parameters' => [ 19 | 'expires' => 'expires', 20 | 'signature' => 'signature', 21 | ], 22 | ]; 23 | -------------------------------------------------------------------------------- /.github/workflows/fix-php-code-style-issues.yml: -------------------------------------------------------------------------------- 1 | name: Fix PHP code style issues 2 | 3 | on: 4 | push: 5 | paths: 6 | - '**.php' 7 | 8 | jobs: 9 | php-code-styling: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - name: Checkout code 14 | uses: actions/checkout@v3 15 | with: 16 | ref: ${{ github.head_ref }} 17 | 18 | - name: Fix PHP code style issues 19 | uses: aglipanci/laravel-pint-action@2.2.0 20 | 21 | - name: Commit changes 22 | uses: stefanzweifel/git-auto-commit-action@v4 23 | with: 24 | commit_message: Fix styling 25 | -------------------------------------------------------------------------------- /.github/workflows/update-changelog.yml: -------------------------------------------------------------------------------- 1 | name: "Update Changelog" 2 | 3 | on: 4 | release: 5 | types: [released] 6 | 7 | jobs: 8 | update: 9 | runs-on: ubuntu-latest 10 | 11 | steps: 12 | - name: Checkout code 13 | uses: actions/checkout@v3 14 | with: 15 | ref: main 16 | 17 | - name: Update Changelog 18 | uses: stefanzweifel/changelog-updater-action@v1 19 | with: 20 | latest-version: ${{ github.event.release.name }} 21 | release-notes: ${{ github.event.release.body }} 22 | 23 | - name: Commit updated CHANGELOG 24 | uses: stefanzweifel/git-auto-commit-action@v4 25 | with: 26 | branch: main 27 | commit_message: Update CHANGELOG 28 | file_pattern: CHANGELOG.md 29 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | app/ 6 | 7 | 8 | 9 | 10 | ./tests/ 11 | ./tests/Commands/ 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) Spatie bvba 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/UrlSignerServiceProvider.php: -------------------------------------------------------------------------------- 1 | name('laravel-url-signer') 16 | ->hasConfigFile() 17 | ->hasCommands([ 18 | GenerateUrlSignerSignatureKey::class, 19 | ]); 20 | } 21 | 22 | public function registeringPackage(): void 23 | { 24 | $this->app->singleton(BaseUrlSigner::class, function () { 25 | $config = config('url-signer'); 26 | 27 | return new UrlSigner( 28 | $config['signature_key'], 29 | $config['parameters']['expires'], 30 | $config['parameters']['signature'] 31 | ); 32 | }); 33 | 34 | $this->app->alias(BaseUrlSigner::class, 'url-signer'); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /.github/workflows/run-tests.yml: -------------------------------------------------------------------------------- 1 | name: run-tests 2 | 3 | on: 4 | - push 5 | - pull_request 6 | 7 | jobs: 8 | test: 9 | runs-on: ${{ matrix.os }} 10 | 11 | strategy: 12 | fail-fast: false 13 | matrix: 14 | os: [ubuntu-latest, windows-latest] 15 | php: [8.2, 8.1] 16 | laravel: ['10.*', '11.*', '12.*'] 17 | stability: [prefer-stable] 18 | include: 19 | - laravel: 10.* 20 | testbench: 8.* 21 | - laravel: 11.* 22 | testbench: 9.* 23 | - laravel: 12.* 24 | testbench: 10.* 25 | exclude: 26 | - laravel: 11.* 27 | php: 8.1 28 | - laravel: 12.* 29 | php: 8.1 30 | 31 | name: P${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.stability }} - ${{ matrix.os }} 32 | 33 | steps: 34 | - name: Checkout code 35 | uses: actions/checkout@v3 36 | 37 | - name: Setup PHP 38 | uses: shivammathur/setup-php@v2 39 | with: 40 | php-version: ${{ matrix.php }} 41 | extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, bcmath, soap, intl, gd, exif, iconv, imagick, fileinfo 42 | coverage: none 43 | 44 | - name: Setup problem matchers 45 | run: | 46 | echo "::add-matcher::${{ runner.tool_cache }}/php.json" 47 | echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json" 48 | 49 | - name: Install dependencies 50 | run: | 51 | composer require "laravel/framework:${{ matrix.laravel }}" "orchestra/testbench:${{ matrix.testbench }}" --no-interaction --no-update 52 | composer update --${{ matrix.stability }} --prefer-dist --no-interaction 53 | 54 | - name: Execute tests 55 | run: vendor/bin/pest 56 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "spatie/laravel-url-signer", 3 | "description": "Laravel implementation of spatie/signed-url", 4 | "keywords": [ 5 | "spatie", 6 | "laravel-url-signer" 7 | ], 8 | "homepage": "https://github.com/spatie/laravel-url-signer", 9 | "license": "MIT", 10 | "authors": [ 11 | { 12 | "name": "Sebastian De Deyne", 13 | "email": "sebastian@spatie.be", 14 | "homepage": "https://spatie.be", 15 | "role": "Developer" 16 | } 17 | ], 18 | "require": { 19 | "php": "^8.1", 20 | "illuminate/support": "^10.0|^11.0|^12.0", 21 | "illuminate/console": "^10.10.0|^11.0|^12.0", 22 | "spatie/laravel-package-tools": "^1.13.6", 23 | "spatie/url-signer": "^2.0" 24 | }, 25 | "require-dev": { 26 | "orchestra/testbench": "^8.0|^9.0|^10.0", 27 | "pestphp/pest": "^1.22.2|^1.22|^2.34|^3.7" 28 | }, 29 | "autoload": { 30 | "psr-4": { 31 | "Spatie\\UrlSigner\\Laravel\\": "src" 32 | } 33 | }, 34 | "autoload-dev": { 35 | "psr-4": { 36 | "Spatie\\UrlSigner\\Laravel\\Tests\\": "tests" 37 | } 38 | }, 39 | "extra": { 40 | "laravel": { 41 | "providers": [ 42 | "Spatie\\UrlSigner\\Laravel\\UrlSignerServiceProvider" 43 | ], 44 | "aliases": { 45 | "UrlSigner": "Spatie\\UrlSigner\\Laravel\\Facades\\UrlSigner" 46 | } 47 | } 48 | }, 49 | "config": { 50 | "sort-packages": true, 51 | "allow-plugins": { 52 | "phpstan/extension-installer": true, 53 | "pestphp/pest-plugin": true 54 | } 55 | }, 56 | "minimum-stability": "dev", 57 | "prefer-stable": true 58 | } 59 | -------------------------------------------------------------------------------- /src/Commands/GenerateUrlSignerSignatureKey.php: -------------------------------------------------------------------------------- 1 | option('show')) { 35 | $this->comment($key); 36 | 37 | return; 38 | } 39 | 40 | if (file_exists($path = $this->envPath()) === false) { 41 | $this->displayKey($key); 42 | 43 | return; 44 | } 45 | 46 | if (Str::contains(file_get_contents($path), 'URL_SIGNER_SIGNATURE_KEY') === false) { 47 | // create new entry 48 | file_put_contents($path, PHP_EOL."URL_SIGNER_SIGNATURE_KEY=$key".PHP_EOL, FILE_APPEND); 49 | } else { 50 | if ($this->option('always-no')) { 51 | $this->comment('Secret key already exists. Skipping...'); 52 | 53 | return; 54 | } 55 | 56 | if ($this->isConfirmed() === false) { 57 | $this->comment('Phew... No changes were made to your secret key.'); 58 | 59 | return; 60 | } 61 | 62 | // update existing entry 63 | file_put_contents($path, str_replace( 64 | 'URL_SIGNER_SIGNATURE_KEY='.$this->laravel['config']['url-signer.signature_key'], 65 | 'URL_SIGNER_SIGNATURE_KEY='.$key, 66 | file_get_contents($path) 67 | )); 68 | } 69 | 70 | $this->displayKey($key); 71 | 72 | } 73 | 74 | /** 75 | * Display the key. 76 | */ 77 | protected function displayKey(string $key): void 78 | { 79 | $this->laravel['config']['url-signer.signature_key'] = $key; 80 | 81 | $this->info("Url-signer key [$key] set successfully."); 82 | 83 | } 84 | 85 | /** 86 | * Check if the modification is confirmed. 87 | */ 88 | protected function isConfirmed(): bool 89 | { 90 | return $this->option('force') || $this->confirm( 91 | 'This will invalidate all existing tokens. Are you sure you want to override the secret key?' 92 | ); 93 | } 94 | 95 | /** 96 | * Get the .env file path. 97 | */ 98 | protected function envPath(): string 99 | { 100 | if (method_exists($this->laravel, 'environmentFilePath')) { 101 | return $this->laravel->environmentFilePath(); 102 | } 103 | 104 | return $this->laravel->basePath('.env'); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to `laravel-url-signer` will be documented in this file. 4 | 5 | ## 3.2.1 - 2025-02-21 6 | 7 | ### What's Changed 8 | 9 | * Laravel 12.x Compatibility by @laravel-shift in https://github.com/spatie/laravel-url-signer/pull/55 10 | 11 | **Full Changelog**: https://github.com/spatie/laravel-url-signer/compare/3.2.0...3.2.1 12 | 13 | ## 3.2.0 - 2024-05-14 14 | 15 | ### What's Changed 16 | 17 | * Implement Key Generation Command for Enhanced Security by @NoahNxT in https://github.com/spatie/laravel-url-signer/pull/54 18 | 19 | ### New Contributors 20 | 21 | * @NoahNxT made their first contribution in https://github.com/spatie/laravel-url-signer/pull/54 22 | 23 | **Full Changelog**: https://github.com/spatie/laravel-url-signer/compare/3.1.0...3.2.0 24 | 25 | ## 3.1.0 - 2024-02-28 26 | 27 | ### What's Changed 28 | 29 | * Bump aglipanci/laravel-pint-action from 1.0.0 to 2.2.0 by @dependabot in https://github.com/spatie/laravel-url-signer/pull/48 30 | * Bump actions/checkout from 2 to 3 by @dependabot in https://github.com/spatie/laravel-url-signer/pull/47 31 | * Laravel 11.x Compatibility by @laravel-shift in https://github.com/spatie/laravel-url-signer/pull/53 32 | 33 | ### New Contributors 34 | 35 | * @dependabot made their first contribution in https://github.com/spatie/laravel-url-signer/pull/48 36 | 37 | **Full Changelog**: https://github.com/spatie/laravel-url-signer/compare/3.0.1...3.1.0 38 | 39 | ## 3.0.1 - 2023-01-17 40 | 41 | ### What's Changed 42 | 43 | - Laravel 10.x Compatibility by @laravel-shift in https://github.com/spatie/laravel-url-signer/pull/46 44 | 45 | ### New Contributors 46 | 47 | - @laravel-shift made their first contribution in https://github.com/spatie/laravel-url-signer/pull/46 48 | 49 | **Full Changelog**: https://github.com/spatie/laravel-url-signer/compare/3.0.0...3.0.1 50 | 51 | ## 2.7.0 - 2022-01-21 52 | 53 | - allow Laravel 9 54 | 55 | ## 2.6.1 - 2021-01-20 56 | 57 | - allow PHP 8 58 | 59 | ## 2.6.0 - 2020-09-09 60 | 61 | - add support for Laravel 8 62 | 63 | ## 2.5.0 - 2020-03-04 64 | 65 | - add support for Laravel 7 66 | 67 | ## 2.4.0 - 2019-09-04 68 | 69 | - add support for Laravel 6 70 | 71 | ## 2.3.0 - 2019-02-27 72 | 73 | - drop support for Laravel 5.7 and below 74 | - drop support for PHP 7.1 and below 75 | 76 | ## 2.2.2 - 2019-02-27 77 | 78 | - Added: Laravel 5.8 compatibility 79 | 80 | ## 2.2.1 - 2018-09-04 81 | 82 | - Added: Laravel 5.7 compatibility 83 | 84 | ## 2.2.0 - 2018-02-08 85 | 86 | - Added: Laravel 5.6 compatibility 87 | 88 | ## 2.1.1 - 2017-11-07 89 | 90 | - Changed: Avoid using `config` in config file 91 | 92 | ## 2.1.0 - 2017-08-13 93 | 94 | - Added: Laravel 5.5 compatibility 95 | 96 | ## 2.0.0 - 2017-02-09 97 | 98 | - Added: Laravel 5.4 compatibility 99 | - Removed: Dropped support for older Laravel versions 100 | 101 | ## 1.1.3 - 2016-08-23 102 | 103 | - Added: Laravel 5.3 compatibility 104 | 105 | ## 1.1.2 - 2016-01-13 106 | 107 | - Fixed: A security vulnerability with the signature key 108 | 109 | ## 1.1.1 - 2016-01-04 110 | 111 | - Fixed: Explicit Laravel version constraints 112 | - Fixed: Moved config file to standardized location 113 | 114 | ## 1.1.0 - 2015-12-22 115 | 116 | - Added: Laravel 5.2 compatibility 117 | 118 | ## 1.0.0 - 2015-08-15 119 | 120 | - Initial release 121 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Create signed URLs with a limited lifetime in Laravel 2 | 3 | [![Latest Version on Packagist](https://img.shields.io/packagist/v/spatie/laravel-url-signer.svg?style=flat-square)](https://packagist.org/packages/spatie/laravel-url-signer) 4 | [![Build Status](https://img.shields.io/travis/spatie/laravel-url-signer.svg?style=flat-square)](https://travis-ci.org/spatie/laravel-url-signer) 5 | [![Quality Score](https://img.shields.io/scrutinizer/g/spatie/laravel-url-signer.svg?style=flat-square)](https://scrutinizer-ci.com/g/spatie/laravel-url-signer) 6 | [![Total Downloads](https://img.shields.io/packagist/dt/spatie/laravel-url-signer.svg?style=flat-square)](https://packagist.org/packages/spatie/laravel-url-signer) 7 | 8 | This package can create URLs with a limited lifetime. This is done by adding an expiration date and a signature to the URL. 9 | 10 | The difference with [Laravel's native route signing](https://laravel.com/docs/master/urls#signed-urls) is that using this package: 11 | 12 | - you can easily use signed URLs between different apps 13 | - the signing secret used is not tied to the app key 14 | - you can easily sign any URL (and not only a route belonging to your app) 15 | 16 | This is how you can create signed URL that's valid for 30 days: 17 | 18 | ```php 19 | use Spatie\UrlSigner\Laravel\Facades\UrlSigner; 20 | 21 | UrlSigner::sign('https://myapp.com/protected-route', now()->addDays(30)); 22 | ``` 23 | 24 | The output will look like this: 25 | 26 | ``` 27 | https://app.com/protected-route?expires=xxxxxx&signature=xxxxxx 28 | ``` 29 | 30 | The URL can be validated with the `validate`-function. 31 | 32 | ```php 33 | // returns `true` if the signed URL is valid, `false` if not 34 | UrlSigner::validate('https://app.com/protected-route?expires=xxxxxx&signature=xxxxxx'); 35 | ``` 36 | 37 | The package also provides [a middleware to protect routes](https://github.com/spatie/laravel-url-signer#protecting-routes-with-middleware). 38 | 39 | ## Support us 40 | 41 | [](https://spatie.be/github-ad-click/laravel-url-signer) 42 | 43 | We invest a lot of resources into creating [best in class open source packages](https://spatie.be/open-source). You can support us by [buying one of our paid products](https://spatie.be/open-source/support-us). 44 | 45 | We highly appreciate you sending us a postcard from your hometown, mentioning which of our package(s) you are using. You'll find our address on [our contact page](https://spatie.be/about-us). We publish all received postcards on [our virtual postcard wall](https://spatie.be/open-source/postcards). 46 | 47 | ## Installation 48 | 49 | As you would have guessed the package can be installed via composer: 50 | 51 | ```bash 52 | composer require spatie/laravel-url-signer 53 | ``` 54 | 55 | You must set an environment variable called `URL_SIGNER_SIGNATURE_KEY` and set it to a long secret value. This value will be used to sign and validate signed URLs. 56 | 57 | ``` 58 | php artisan generate:url-signer-signature-key 59 | {--s|show : Display the key instead of modifying files.} 60 | {--always-no : Skip generating key if it already exists.} 61 | {--f|force : Skip confirmation when overwriting an existing key.} 62 | ``` 63 | 64 | The configuration file can optionally be published via: 65 | 66 | ```bash 67 | php artisan vendor:publish --tag="url-signer-config" 68 | ``` 69 | 70 | This is the content of the file: 71 | 72 | ```php 73 | return [ 74 | /* 75 | * This string is used the to generate a signature. You should 76 | * keep this value secret. 77 | */ 78 | 'signature_key' => env('URL_SIGNER_SIGNATURE_KEY'), 79 | 80 | /* 81 | * The default expiration time of a URL in seconds. 82 | */ 83 | 'default_expiration_time_in_seconds' => 60 * 60 * 24, 84 | 85 | /* 86 | * These strings are used a parameter names in a signed url. 87 | */ 88 | 'parameters' => [ 89 | 'expires' => 'expires', 90 | 'signature' => 'signature', 91 | ], 92 | ]; 93 | ``` 94 | ## Usage 95 | 96 | URL's can be signed with the `sign`-method: 97 | 98 | ```php 99 | use Spatie\UrlSigner\Laravel\Facades\UrlSigner; 100 | 101 | UrlSigner::sign('https://myapp.com/protected-route'); 102 | ``` 103 | 104 | By default, the lifetime of an URL is one day. This value can be change in the config file. 105 | If you want a custom lifetime, you can specify the number of days the URL should be valid: 106 | 107 | ```php 108 | use Spatie\UrlSigner\Laravel\Facades\UrlSigner; 109 | 110 | // the generated URL will be valid for 5 minutes. 111 | UrlSigner::sign('https://myapp.com/protected-route', now()->addMinutes(5)); 112 | 113 | // alternatively you could also pass the amount of seconds 114 | UrlSigner::sign('https://myapp.com/protected-route', 60 * 5); 115 | ``` 116 | 117 | ### Validating URLs 118 | 119 | To validate a signed URL, simply call the `validate()`-method. This method returns a boolean. 120 | 121 | ```php 122 | use Spatie\UrlSigner\Laravel\Facades\UrlSigner; 123 | 124 | UrlSigner::validate('https://app.com/protected-route?expires=xxxxxx&signature=xxxxxx'); 125 | ``` 126 | 127 | ### Protecting routes with middleware 128 | 129 | The package provides a middleware to protect routes. 130 | 131 | To use it you must first register the `Spatie\UrlSigner\Laravel\Middleware\ValidateSignature` as route middleware in your HTTP kernel. 132 | 133 | ```php 134 | // in app/Http/Kernel.php 135 | 136 | protected $routeMiddleware = [ 137 | // ... 138 | 'signed-url' => \Spatie\UrlSigner\Laravel\Middleware\ValidateSignature::class, 139 | ]; 140 | ``` 141 | 142 | Next, you can apply it on any route you want. 143 | 144 | ```php 145 | Route::get('protected-route', fn () => 'Hello secret world!') 146 | ->middleware('signed-url'); 147 | ``` 148 | 149 | Your app will abort with a 403 status code if the route is called without a valid signature. 150 | 151 | ## Changelog 152 | 153 | Please see [CHANGELOG](CHANGELOG.md) for more information what has changed recently. 154 | 155 | ## Testing 156 | 157 | You can run the test using this command: 158 | 159 | ```bash 160 | composer test 161 | ``` 162 | 163 | ## Usage outside Laravel 164 | 165 | If you're working on a non-Laravel project, you can use the [framework agnostic version](https://github.com/spatie/url-signer). 166 | 167 | ## Contributing 168 | 169 | Please see [CONTRIBUTING](https://github.com/spatie/.github/blob/main/CONTRIBUTING.md) for details. 170 | 171 | ## Security 172 | 173 | If you've found a bug regarding security please mail [security@spatie.be](mailto:security@spatie.be) instead of using the issue tracker. 174 | 175 | ## Postcardware 176 | 177 | You're free to use this package, but if it makes it to your production environment we highly appreciate you sending us a postcard from your hometown, mentioning which of our package(s) you are using. 178 | 179 | Our address is: Spatie, Kruikstraat 22, 2018 Antwerp, Belgium. 180 | 181 | We publish all received postcards [on our company website](https://spatie.be/en/opensource/postcards). 182 | 183 | ## Credits 184 | 185 | - [Freek Van der Herten](https://github.com/freekmurze) 186 | - [Sebastian De Deyne](https://github.com/sebastiandedeyne) 187 | - [All Contributors](../../contributors) 188 | 189 | ## License 190 | 191 | The MIT License (MIT). Please see [License File](LICENSE.md) for more information. 192 | --------------------------------------------------------------------------------