Weave Code
Code Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Url Signer Laravel Package

spatie/laravel-url-signer

Sign and validate any URL in Laravel with an expiring signature. Works across apps, uses a configurable secret (not the app key), and includes middleware to protect routes. Generate time-limited links in one call and verify them anywhere.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup

  1. Installation:

    composer require spatie/laravel-url-signer
    

    Publish the config (optional):

    php artisan vendor:publish --provider="Spatie\UrlSigner\UrlSignerServiceProvider"
    
  2. First Use Case: Sign a URL with a 30-day expiry:

    use Spatie\UrlSigner\Laravel\Facades\UrlSigner;
    
    $signedUrl = UrlSigner::sign('https://example.com/protected', now()->addDays(30));
    

    Output:

    https://example.com/protected?expires=1735689600&signature=abc123...
    
  3. Validation:

    $isValid = UrlSigner::validate($signedUrl); // Returns bool
    

Where to Look First

  • Config: config/urlsigner.php (for custom signing keys, expiry defaults, etc.).
  • Facade: UrlSigner facade for quick usage.
  • Middleware: Built-in ValidateSignedUrl middleware for route protection.

Implementation Patterns

Core Workflows

  1. Signing URLs:

    // Basic signing
    $url = UrlSigner::sign('https://example.com/download', now()->addHours(1));
    
    // Sign with custom key (from config)
    $url = UrlSigner::sign('https://example.com/secret', now()->addDays(7), 'custom_key');
    
  2. Route Protection: Add middleware to a route:

    Route::get('/download', function () {
        return response()->download('file.pdf');
    })->middleware('signed-url');
    

    Or globally in app/Http/Kernel.php:

    protected $middleware = [
        // ...
        \Spatie\UrlSigner\Middleware\ValidateSignedUrl::class,
    ];
    
  3. Dynamic Expiry:

    $expiry = now()->addMinutes(config('urlsigner.default_expiry_minutes'));
    $url = UrlSigner::sign('/protected', $expiry);
    
  4. Signing Non-Route URLs:

    // External API URLs, S3 presigned URLs, etc.
    $s3Url = UrlSigner::sign('https://my-bucket.s3.amazonaws.com/private-file', now()->addHours(2));
    

Integration Tips

  • API Responses: Return signed URLs in JSON responses for secure downloads:
    return response()->json(['download_url' => UrlSigner::sign('/download', now()->addHours(1))]);
    
  • Queue Jobs: Sign URLs for delayed processing (e.g., email attachments):
    $job = new SendEmailWithAttachment($user, UrlSigner::sign('/attachments/123', now()->addHours(1)));
    dispatch($job);
    
  • Blade Templates:
    <a href="{{ Spatie\UrlSigner\Laravel\Facades\UrlSigner::sign('/profile', now()->addDays(1)) }}">
        Secure Link
    </a>
    

Gotchas and Tips

Pitfalls

  1. Clock Skew:

    • URLs expire based on the server's clock. If the server clock drifts, URLs may fail validation.
    • Fix: Use a time synchronization service (e.g., NTP) or add a buffer to expiry times:
      $expiry = now()->addMinutes(30)->addSeconds(30); // Extra buffer
      
  2. Key Management:

    • Default key is config('urlsigner.key'). If not set, it falls back to APP_KEY.
    • Tip: Use environment variables for keys:
      'key' => env('URL_SIGNER_KEY', 'default_key_here'),
      
  3. URL Length Limits:

    • Long URLs (e.g., with complex signatures) may exceed HTTP limits (e.g., 2048 chars).
    • Tip: Shorten base URLs or use URL shorteners before signing.
  4. Middleware Scope:

    • The signed-url middleware validates all routes by default. Use route-specific middleware to avoid unintended blocks:
      Route::get('/public', function () {})->withoutMiddleware('signed-url');
      

Debugging

  • Validation Failures:

    • Check the expires timestamp is in the future (not past).
    • Verify the signature matches the expected hash (use dd() to inspect the URL parts):
      $parts = parse_url($signedUrl);
      parse_str($parts['query'], $query);
      dd($query); // Inspect expires/signature
      
    • Ensure the signing key matches between generation and validation.
  • Log Expiry Warnings: Add a custom validator to log near-expiry URLs:

    UrlSigner::extend(function ($url, $expiry) {
        if ($expiry->diffInMinutes() < 5) {
            Log::warning("URL near expiry: {$url}");
        }
    });
    

Extension Points

  1. Custom Signing Logic: Override the default HMAC signing:

    UrlSigner::extend(function ($url, $expiry, $key) {
        return "custom-signed-{$url}?expires={$expiry->timestamp}";
    });
    
  2. Additional Query Parameters: Append extra data to URLs (e.g., user IDs):

    $url = UrlSigner::sign(
        'https://example.com/file',
        now()->addHours(1),
        'key',
        ['user_id' => 123]
    );
    

    Note: These params are not signed by default. Use with caution.

  3. Rate Limiting: Combine with Laravel's rate limiting to prevent abuse:

    Route::get('/download', function () {
        return response()->download('file.pdf');
    })->middleware(['signed-url', 'throttle:10,1']);
    
  4. Testing: Mock the UrlSigner facade in tests:

    UrlSigner::shouldReceive('validate')
        ->once()
        ->with('https://example.com/protected?expires=...')
        ->andReturn(true);
    
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
davejamesmiller/laravel-breadcrumbs
artisanry/parsedown
christhompsontldr/phpsdk
enqueue/dsn
bunny/bunny
enqueue/test
enqueue/null
enqueue/amqp-tools
milesj/emojibase
bower-asset/punycode
bower-asset/inputmask
bower-asset/jquery
bower-asset/yii2-pjax
laravel/nova
spatie/laravel-mailcoach
spatie/laravel-superseeder
laravel/liferaft
nst/json-test-suite
danielmiessler/sec-lists
jackalope/jackalope-transport