├── .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 | [](https://packagist.org/packages/spatie/laravel-url-signer)
4 | [](https://travis-ci.org/spatie/laravel-url-signer)
5 | [](https://scrutinizer-ci.com/g/spatie/laravel-url-signer)
6 | [](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 |
--------------------------------------------------------------------------------