PHPackages                             snicco/signed-url - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Authentication &amp; Authorization](/categories/authentication)
4. /
5. snicco/signed-url

ActiveLibrary[Authentication &amp; Authorization](/categories/authentication)

snicco/signed-url
=================

A small, framework agnostic library to create and validate signed-urls.

v1.10.1(1y ago)22.1k4LGPL-3.0-onlyPHPPHP ^7.4|^8.0

Since Apr 17Pushed 1y ago1 watchersCompare

[ Source](https://github.com/snicco/signed-url)[ Packagist](https://packagist.org/packages/snicco/signed-url)[ RSS](/packages/snicco-signed-url/feed)WikiDiscussions master Synced 1mo ago

READMEChangelogDependencies (5)Versions (35)Used By (4)

A secure, modular and framework-agnostic PHP library to sign and verify urls.
=============================================================================

[](#a-secure-modular-and-framework-agnostic-php-library-to-sign-and-verify-urls)

[![codecov](https://camo.githubusercontent.com/a99e6ec528fffd1664e95534f9a09a4a09d2afe62799ff0d8774dc22d8453f6c/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f436f7665726167652d3130302532352d73756363657373)](https://app.codecov.io/gh/snicco/snicco)[![Psalm Type-Coverage](https://camo.githubusercontent.com/c12cfed65c7da16501f7a84e7861b8c4757fc30e9dc00bb2983783dbb3f3f84c/68747470733a2f2f73686570686572642e6465762f6769746875622f736e6963636f2f736e6963636f2f636f7665726167652e7376673f)](https://shepherd.dev/github/snicco/snicco)[![Psalm level](https://camo.githubusercontent.com/c5e90ffcf3a5aa1f78f93bddde5db7627b114329393aa87697df8cedc7f5391a/68747470733a2f2f73686570686572642e6465762f6769746875622f736e6963636f2f736e6963636f2f6c6576656c2e7376673f)](https://psalm.dev/)[![PhpMetrics - Static Analysis](https://camo.githubusercontent.com/364ffb28ea219affd0fed2e99cc046bac0bf41da3f1d3814e0cbe4a4bb54c994/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f5068704d6574726963732d5374617469635f416e616c797369732d326561343466)](https://snicco.github.io/snicco/phpmetrics/SignedUrl/index.html)[![PHP-Versions](https://camo.githubusercontent.com/241a10d25aa09d5e8a82ebd2b55780a63dd43736d958d4004c3166e650874aca/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f5048502d253545372e34253743253545382e30253743253545382e312d626c7565)](https://camo.githubusercontent.com/241a10d25aa09d5e8a82ebd2b55780a63dd43736d958d4004c3166e650874aca/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f5048502d253545372e34253743253545382e30253743253545382e312d626c7565)

Table of contents
-----------------

[](#table-of-contents)

1. [Motivation](#motivation)
2. [Installation](#how-to-install)
3. [Usage](#how-to-use)
    1. [Creating a secret](#creating-a-secret)
    2. [Creating a signed-url](#creating-a-signed-url)
    3. [Validating a signed url](#validating-a-signed-url)
        1. [PSR-15 middleware](#psr-15-middleware)
        2. [Other PHP applications](#all-php-apps)
    4. [Storage types](#storage-types)
        1. [Session](#sessionstorage-included)
        2. [Null](#nullstorage-included)
        3. [InMemory](#inmemory-included)
        4. [PSR-16](#psr16-cache-bridge-package)
        5. [Implement your own](#implementing-your-own-storage)
4. [Contributing](#contributing)
5. [Issues and PR's](#reporting-issues-and-sending-pull-requests)
6. [Security](#security)

Motivation
----------

[](#motivation)

While developing the [**Snicco** project](https://github.com/snicco/snicco) we couldn't find any good **standalone PHP-libraries** for signing urls. We needed this functionality in a couple of places, so we decided to roll our own implementation.

Features:

- Uses strong, random secrets, generated by a [CSPRNG](https://en.wikipedia.org/wiki/Cryptographically-secure_pseudorandom_number_generator) and secure hash functions.
- Validates the signature, the expiration and an enforced usage-limit on a per url basis.
- PSR-7/15 compatible. No hidden dependencies on PHP super globals.
- Protects against [timing based side-channel attacks](https://blog.ircmaxell.com/2014/11/its-all-about-time.html)
- Permanently invalidates a signed-url after the max usage. (Rotating your secret invalidates all signed-urls)
- Defensively programmed, making incorrect usage very hard.
- Support for multiple storage backends.
- A properly tested and straightforward API.

While the term `signed-url` is technically incorrect (this package uses HMACs, not asymmetric signatures), we chose to stick to the way [Symfony](https://symfony.com/blog/new-in-symfony-5-1-improved-urisigner) and [Laravel](https://laravel.com/docs/9.x/urls#signed-urls) name it.

Installation
------------

[](#installation)

```
composer require snicco/signed-url
```

Usage
-----

[](#usage)

### Creating a secret

[](#creating-a-secret)

Run the following command from your project root and store the generated secret in a secure location that is outside your web root.

```
vendor/bin/generate-signed-url-secret
```

This will output a random, hex-encoded secret that looks like this: `32|1e21be67f2279e485c7c5e8291d05edda7e76ffb01ddb8eb290ce826528ad2ff`

**This secret should NEVER be stored in version control.**

In your application, load the secret from an environment variable in your application using something like [`symfony/dotenv`](https://github.com/symfony/dotenv).

```
// require 'vendor/autoload.php';
$secret = \Snicco\Component\SignedUrl\Secret::fromHexEncoded(getenv('SIGNED_URL_SECRET'));
```

---

### Creating a signed-url

[](#creating-a-signed-url)

```
$secret = /* */

$hmac = new Snicco\Component\SignedUrl\HMAC($secret, 'sha256')

// This is a simple interface.
// Use one of the inbuilt storages in the #storages section or provide your own.
$storage = /* */

$signer = new \Snicco\Component\SignedUrl\UrlSigner($storage, $hmac);

// The maximum lifetime in seconds that this link should be valid for.
$lifetime_in_sec = 60;

// The maximum amount that this link should be valid for.
// After each successfully validation this amount will be decreased by 1.
$usage_limit = 1;

// optional: adding request context that must be the same in order to
// successfully validate a signed-url.
$context = ($_SERVER['REMOTE_ADDR'] ?? '') . ($_SERVER['HTTP_USER_AGENT'] ?? '');

$signed_url = $signer->sign('https://example.com/unsubscribe?user_id=12' , $lifetime_in_sec, $usage_limit, $context);

$mailer = /* */

$href = $signed_url->asString();

// $href will be something like transformers:
// https://example.com/unsubscribe?user_id=12expires=1639783661&signature=Del1cGmLB1wVET6PJieCrQ==|1MTBBGIpEGPVuGaKDjjrHDBusMNoWB15Ng5lKBSSLQY=

$mailer->send('user12@gmail.com', "Click  here  to unsubscribe.")
```

---

### Validating a signed-url

[](#validating-a-signed-url)

Validation of signed-urls should be performed in a middleware to avoid boilerplate.

The code samples below describe the **manual** way to validate urls in any **PHP** application.

#### PSR-15 middleware

[](#psr-15-middleware)

If your favorite framework is PSR-7/PSR-15 compatible and supports middleware on a per-route basis, you can use our [PSR-15 middleware bridge](https://github.com/snicco/signed-url-psr15-bridge)which makes this dead simple.

#### All PHP apps

[](#all-php-apps)

```
$storage = /* */
$hmac = /* */

// Clean expired links periodically.
try {
    // 0-100
    $percentage = 2;
   \Snicco\Component\SignedUrl\GarbageCollector::clean($storage, $percentage);

} catch (UnavailableStorage $e) {
    // gc did not work for some reason. Log and continue.
    error_log($e->getMessage());

}

$validator = new \Snicco\Component\SignedUrl\SignedUrlValidator($storage, $hmac);

$target = $_SERVER['REQUEST_URI'].'?'.$_SERVER['QUERY_STRING'];

try {

    // optional context, has to be the same scheme used at creation.
    $context = ($_SERVER['REMOTE_ADDR'] ?? '') . ($_SERVER['HTTP_USER_AGENT'] ?? '');

    $validator->validate( $target, $context);

} catch (\Snicco\Component\SignedUrl\Exception\InvalidSignature $e ) {

   error_log("invalid signature.");
   echo "This link has expired. Please request a new one."

} catch (\Snicco\Component\SignedUrl\Exception\SignedUrlExpired $e ) {

   error_log("signed url expired.");
   echo "This link has expired. Please request a new one."

} catch (\Snicco\Component\SignedUrl\Exception\SignedUrlUsageExceeded $e ) {

   error_log("signed url usage exceeded.");
   echo "This link has expired. Please request a new one."
}

// Everything is valid.
// If the link can be used multiple times the usage is decremented automatically by 1.
echo "You have been unsubscribed."
```

---

### Storage types

[](#storage-types)

The [`Snicco\SignedUrl\Contracts\SingedUrlStorage`](src/Storage/SignedUrlStorage.php) keeps an identifier for each signed-url that is created and ensures that your max usage limits are enforced.

Without some form of backend storage, signed-urls are valid any number of times until the expiration timestamp is passed. (If this is what you want you can use the [`NullStorage`](src/Storage/NullStorage.php)).

---

#### SessionStorage (included):

[](#sessionstorage-included)

The [`SessionStorage`](src/Storage/SessionStorage.php) accepts an `array` or any object that implements `ArrayAccess` (**passed by reference**).

```
// using an array.
$storage = new \Snicco\Component\SignedUrl\Storage\SessionStorage($_SESSION);

// using an object implementing ArrayAccess
$arr = new MyArrayAccess();
$storage = new \Snicco\Component\SignedUrl\Storage\SessionStorage($arr);
```

---

#### NullStorage (included)

[](#nullstorage-included)

The [`NullStorage`](src/Storage/NullStorage.php) does nothing. No signed-urls will be stored and no usage limits are enforced. Use this only if your signed-urls should be valid any number of times before expiring.

Validity of a signed-url will be based solely on the correct signature and expriation timestamp.

---

#### InMemory (included):

[](#inmemory-included)

You can use the [`InMemoryStorage`](src/Storage/InMemoryStorage.php) during unit tests.

```
$storage = new \Snicco\Component\SignedUrl\Storage\InMemoryStorage()
```

---

#### PSR16-Cache (bridge package):

[](#psr16-cache-bridge-package)

We have a dedicated [PSR-16 bridge](https://github.com/snicco/signed-url-psr16-bridge)that will allow you to use any **PSR-16 cache** as a storage.

---

#### Implementing your own storage:

[](#implementing-your-own-storage)

Implementing your own storage is very easy. You only have to implement the simple [`SingedUrlStorage`](src/Storage/SignedUrlStorage.php) interface.

Use the [`snicco/signed-url-testing` package](https://github.com/snicco/signed-url-testing)to test your implementation against the contract of the interface.

Contributing
------------

[](#contributing)

This repository is a read-only split of the development repo of the [**Snicco** project](https://github.com/snicco/snicco).

[This is how you can contribute](https://github.com/snicco/snicco/blob/master/CONTRIBUTING.md).

Reporting issues and sending pull requests
------------------------------------------

[](#reporting-issues-and-sending-pull-requests)

Please report issues in the [**Snicco** monorepo](https://github.com/snicco/snicco/blob/master/CONTRIBUTING.md##using-the-issue-tracker).

Security
--------

[](#security)

If you discover a security vulnerability, please follow our [disclosure procedure](https://github.com/snicco/snicco/blob/master/SECURITY.md).

###  Health Score

36

—

LowBetter than 82% of packages

Maintenance35

Infrequent updates — may be unmaintained

Popularity18

Limited adoption so far

Community13

Small or concentrated contributor base

Maturity65

Established project with proven stability

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~27 days

Recently: every ~1 days

Total

33

Last Release

613d ago

Major Versions

v1.10.0 → v2.0.0-beta.12024-09-01

v1.10.1 → v2.0.0-beta.72024-09-04

### Community

Maintainers

![](https://www.gravatar.com/avatar/d659a021c9077ceb60e6ab1ea4287b9f209edb97e93bceceb7fb539d8302e894?d=identicon)[calvinalkan](/maintainers/calvinalkan)

---

Top Contributors

[![snicco-bot](https://avatars.githubusercontent.com/u/101470239?v=4)](https://github.com/snicco-bot "snicco-bot (25 commits)")

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/snicco-signed-url/health.svg)

```
[![Health](https://phpackages.com/badges/snicco-signed-url/health.svg)](https://phpackages.com/packages/snicco-signed-url)
```

###  Alternatives

[pragmarx/google2fa

A One Time Password Authentication package, compatible with Google Authenticator.

2.0k82.4M164](/packages/pragmarx-google2fa)[spomky-labs/otphp

A PHP library for generating one time passwords according to RFC 4226 (HOTP Algorithm) and the RFC 6238 (TOTP Algorithm) and compatible with Google Authenticator

1.5k46.1M119](/packages/spomky-labs-otphp)[paragonie/halite

High-level cryptography interface powered by libsodium

1.2k9.4M63](/packages/paragonie-halite)[laragear/two-factor

On-premises 2FA Authentication for out-of-the-box.

339785.3k8](/packages/laragear-two-factor)[web-auth/webauthn-lib

FIDO2/Webauthn Support For PHP

1195.3M72](/packages/web-auth-webauthn-lib)[web-auth/webauthn-framework

FIDO2/Webauthn library for PHP and Symfony Bundle.

50570.7k1](/packages/web-auth-webauthn-framework)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
