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

Url Signature Laravel Package

dsentker/url-signature

Laravel/PHP package to create and verify signed URLs. Add a signature to query strings to protect routes and parameters from tampering, with simple helpers for generating signatures and validating incoming requests, including optional expiry support.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup

  1. Installation:

    composer require dsentker/url-signature
    

    Add the service provider to config/app.php under providers:

    Dsentker\UrlSignature\UrlSignatureServiceProvider::class,
    
  2. Publish Config (optional):

    php artisan vendor:publish --provider="Dsentker\UrlSignature\UrlSignatureServiceProvider"
    

    This generates config/url-signature.php with default settings.

  3. First Use Case: Generate a signed URL in a controller or blade template:

    use Dsentker\UrlSignature\Facades\UrlSignature;
    
    $signedUrl = UrlSignature::generate(
        url('/payment?amount=100&currency=USD'),
        ['secret' => config('services.payment.secret_key')]
    );
    

    Outputs a URL like:

    https://example.com/payment?amount=100&currency=USD&signature=abc123xyz
    

Implementation Patterns

Core Workflows

  1. Generating Signed URLs:

    // In a controller
    $url = route('checkout', ['id' => 123]);
    $signedUrl = UrlSignature::generate($url, ['secret' => 'your_key']);
    
    // In Blade
    @php
        $signedUrl = UrlSignature::generate(
            route('download', ['file' => 'report.pdf']),
            ['secret' => config('app.signature_key')]
        );
    @endphp
    <a href="{{ $signedUrl }}">Download Report</a>
    
  2. Validation Middleware: Add to app/Http/Kernel.php:

    protected $routeMiddleware = [
        'validate.signature' => \Dsentker\UrlSignature\Middleware\ValidateSignature::class,
    ];
    

    Apply to routes:

    Route::get('/secure-endpoint', function () {
        // Logic for validated requests
    })->middleware('validate.signature');
    
  3. Dynamic Secret Keys: Use closures for runtime secrets:

    $signedUrl = UrlSignature::generate(
        url('/admin'),
        ['secret' => fn() => app('auth')->user()->apiKey]
    );
    

Integration Tips

  • Laravel Routes: Pre-sign URLs in route closures:
    Route::get('/signed-route', function () {
        return UrlSignature::generate(
            route('target'),
            ['secret' => 'dynamic_key']
        );
    });
    
  • API Responses: Return signed URLs in JSON:
    return response()->json([
        'data' => $data,
        'download_url' => UrlSignature::generate(
            route('download', $file),
            ['secret' => $user->secret]
        )
    ]);
    
  • Queue Jobs: Sign URLs for delayed processing:
    Dispatch(new ProcessPayment($order))
        ->delay(now()->addMinutes(5))
        ->onQueue('high');
    // In job handle():
    $signedUrl = UrlSignature::generate(
        route('payment.webhook'),
        ['secret' => $order->webhookSecret]
    );
    

Gotchas and Tips

Pitfalls

  1. Secret Management:

    • Hardcoding secrets in Blade templates triggers warnings. Use config or environment variables.
    • Fix: Store secrets in config/services.php or .env.
  2. Query String Order Sensitivity (Critical in 1.1.0):

    • Breaking Change: The package now strictly enforces alphabetical sorting of query parameters before hashing due to changes in the QueryString helper class. This is now the default behavior and cannot be disabled.
    • Impact:
      • If your application or clients rely on custom parameter ordering (e.g., for legacy systems), update all signature generation and validation logic to ensure parameters are sorted alphabetically.
      • If you need to exclude specific parameters (e.g., _token, page), use the exclude option to avoid unintended sorting:
        UrlSignature::generate($url, [
            'secret' => $secret,
            'exclude' => ['_token', 'page'] // Explicitly exclude unsorted params
        ]);
        
    • Validation Impact: Ensure your validation middleware or external clients now sort parameters alphabetically before generating signatures.
  3. Middleware Timing:

    • The ValidateSignature middleware runs after route resolution. If your route requires signed parameters (e.g., ?id=123&signature=...), validate the signature before processing the ID.
    • Fix: Use a custom middleware extending ValidateSignature to reorder logic.
  4. Case Sensitivity: The default HMAC algorithm (SHA256) is case-sensitive. Ensure secrets and generated signatures match exactly.

Debugging

  • Signature Mismatches: Compare the generated signature with the received one:

    $expected = UrlSignature::generate($url, ['secret' => $secret]);
    $received = request()->query('signature');
    dd(hash_equals($expected, $received)); // Prevent timing attacks
    
    • New in 1.1.0: If signatures still mismatch, verify that:
      1. Query parameters are sorted alphabetically in both generation and validation.
      2. No parameters are being excluded inconsistently.
    • Log the sorted parameters for comparison:
      \Log::debug('Generated URL Params', [
          'sorted_params' => UrlSignature::getSortedQueryString($url),
          'raw_params' => request()->query()
      ]);
      
  • Log Raw Inputs: Add to ValidateSignature handler:

    \Log::debug('Signed URL Validation', [
        'url' => $request->fullUrl(),
        'query_params' => $request->query(),
        'signature' => $request->query('signature'),
        'sorted_params' => UrlSignature::getSortedQueryString($request->fullUrl())
    ]);
    

Extension Points

  1. Custom Algorithms: Extend the Dsentker\UrlSignature\Contracts\SignatureGenerator contract:

    class CustomGenerator implements SignatureGenerator {
        public function generate(string $url, string $secret): string {
            return base64_encode(hash_hmac('sha1', $url, $secret, true));
        }
    }
    

    Bind in AppServiceProvider:

    $this->app->bind(SignatureGenerator::class, function () {
        return new CustomGenerator();
    });
    
  2. Parameter Whitelisting: Use the exclude parameter to control included parameters (now more reliable in 1.1.0):

    UrlSignature::generate($url, [
        'secret' => $secret,
        'exclude' => ['_token', 'page', 'sort'] // Ignore these params
    ]);
    
  3. Expiry Support: Add a expires parameter to URLs:

    $url = UrlSignature::generate(
        url('/expire-soon'),
        [
            'secret' => $secret,
            'expires' => now()->addMinutes(10)->timestamp
        ]
    );
    

    Validate in middleware:

    if (request()->query('expires') < time()) {
        abort(403, 'URL expired');
    }
    
  4. Custom Query String Handling (Advanced): If you must deviate from alphabetical sorting (e.g., for legacy systems), override the QueryString helper:

    use Dsentker\UrlSignature\Support\QueryString;
    
    class CustomQueryString extends QueryString {
        protected function sortParams(array $params): array {
            // Implement custom sorting logic (e.g., preserve original order)
            return $params;
        }
    }
    

    Bind the custom class in AppServiceProvider:

    $this->app->bind(\Dsentker\UrlSignature\Contracts\QueryString::class, function () {
        return new CustomQueryString();
    });
    
    • Warning: This bypasses the package's security guarantees. Use only if absolutely necessary and document the deviation clearly.
  5. Testing: Add tests to verify signature generation/validation with sorted parameters:

    public function test_sorted_query_string()
    {
        $url = url('/test?b=2&a=1');
        $signedUrl = UrlSignature::generate($url, ['secret' => 'test']);
    
        $this->assertStringContainsString('a=1&b=2', $signedUrl);
        $this->assertTrue(UrlSignature::validate($signedUrl, ['secret' => 'test']));
    }
    
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.
codifyo/ts-generator-bundle
andydefer/laravel-cluster
testo/fiber
mintobit/jobqueue
a4sex/maintenance-bundle
a4sex/entity-date-update
a4sex/client-identifier
a4sex/base-utilites
a4sex/key-value-storage
a4sex/micro-status
chilldev/dependency-injection-extra
datinglibre/datinglibre-app-api
biberltd/corebundle
bricre/symfony-bundle-test
biberltd/logbundle
dominium/http-adapter-bundle
dominium/google-analytics
a4sex/auto-clean-entity
christhompsontldr/laravel-inky
spatie/mailcoach-vapor