imran / laravel-encrypted-route-params
Encrypt sensitive Laravel route parameters with Crypt and decrypt them before implicit binding.
Package info
github.com/grim-reapper/laravel-encrypted-route-params
pkg:composer/imran/laravel-encrypted-route-params
Requires
- php: ^8.1
- illuminate/console: ^10.0|^11.0|^12.0|^13.0
- illuminate/contracts: ^10.0|^11.0|^12.0|^13.0
- illuminate/database: ^10.0|^11.0|^12.0|^13.0
- illuminate/encryption: ^10.0|^11.0|^12.0|^13.0
- illuminate/http: ^10.0|^11.0|^12.0|^13.0
- illuminate/routing: ^10.0|^11.0|^12.0|^13.0
- illuminate/support: ^10.0|^11.0|^12.0|^13.0
Requires (Dev)
- orchestra/testbench: ^8.22|^9.0|^10.0|^11.0
- phpunit/phpunit: ^10.5|^11.0
README
๐ Stop ID Scraping & Leakage with Transparent URL Encryption
Are you still exposing raw database IDs in your URLs?
https://your-app.com/invoices/1024
The Problem
Exposing raw IDs is a major security and business intelligence risk:
- ID Scraping: Competitors can easily guess next/previous IDs to scrape your data.
- Business Intelligence Leakage: Anyone can see exactly how many orders, users, or invoices you have just by looking at the URL.
- Insecure Direct Object Reference (IDOR): While middleware should prevent unauthorized access, raw IDs make it tempting for attackers to probe for weaknesses.
- Ugly URLs: Raw IDs feel "naked" and less professional than modern, obfuscated identifiers.
The Solution
Laravel Encrypted Route Params provides a seamless, "drop-in" way to encrypt sensitive path parameters using Laravelโs native Crypt facade.
It handles everything automatically:
-
Transparent Encryption: Use
route()as normal; it encrypts the parameters on the fly. -
Automatic Decryption: Middleware decrypts values before implicit route model binding, so your controllers don't even know it happened.
-
Shorter Tokens: Optional compact mode for cleaner, shorter URLs.
-
JSON Support: Can also decrypt tokens inside JSON request payloads.
-
Model-Level Control: Use a Trait to automatically encrypt IDs in all URLs and JSON resources.
-
PHP
^8.1 -
Laravel
^10.0|^11.0|^12.0|^13.0 -
A valid
APP_KEY
๐ Installation
composer require imran/laravel-encrypted-route-params
The package is auto-discovered. If discovery is disabled, register the provider manually:
// config/app.php 'providers' => [ // ... Imran\EncryptedRouteParams\EncryptedRouteParamsServiceProvider::class, ],
โ๏ธ Quick Start
1. Middleware Setup (Required)
Decryption must run before SubstituteBindings.
Laravel 11 / 12 / 13 (bootstrap/app.php)
use Illuminate\Foundation\Configuration\Middleware; use Imran\EncryptedRouteParams\Middleware\DecryptEncryptedRouteParameters; return Application::configure(basePath: dirname(__DIR__)) ->withMiddleware(function (Middleware $middleware) { $middleware->web( remove: [\Illuminate\Routing\Middleware\SubstituteBindings::class], append: [ DecryptEncryptedRouteParameters::class, \Illuminate\Routing\Middleware\SubstituteBindings::class, ], ); })
2. Usage on Routes
Just add ->encrypted() to any route definition:
use Illuminate\Support\Facades\Route; // Encrypt the {invoice} parameter automatically Route::get('/invoices/{invoice}', [InvoiceController::class, 'show']) ->name('invoices.show') ->encrypted();
3. Generate URLs
Just use the standard route() helper! If transparent_url_generation is enabled (default), it just works:
// Generates: https://app.com/invoices/eyJpdiI6Il... (encrypted) $url = route('invoices.show', ['invoice' => $invoice->id]);
๐๏ธ Model-Level Control (Automatic)
Instead of marking every route, you can enable encryption directly on your Eloquent models. This is useful for sensitive IDs that should always be encrypted in URLs and JSON responses.
1. Add the Trait
Add HasEncryptedAttributes to your model and define which attributes should be encrypted:
use Illuminate\Database\Eloquent\Model; use Imran\EncryptedRouteParams\Traits\HasEncryptedAttributes; class Invoice extends Model { use HasEncryptedAttributes; /** * The attributes that should be encrypted in URLs and toArray(). */ protected $encrypted = [ 'id', 'customer_id', ]; }
2. What this does:
- Automatic URLs:
route('invoices.show', $invoice)will now automatically produce an encrypted URL, even if the route is NOT marked with->encrypted(). - Automatic JSON: When you return the model from a controller, use it in a JsonResource, or call
$invoice->toArray(), the listed attributes will be encrypted automatically. - Transparent Decryption: The middleware automatically detects models using this trait and decrypts the route parameters before they reach your controller.
๐ ๏ธ Advanced Features
Selecting Specific Parameters
// Only encrypt 'invoice', leave 'org' as plaintext Route::get('/orgs/{org}/invoices/{invoice}', ...) ->encrypted(only: ['invoice']); // Encrypt everything except the 'slug' Route::get('/u/{user}/{slug}', ...) ->encrypted(except: ['slug']);
Shorter Tokens (cipher = compact)
Tired of long Base64 strings? Switch to compact mode in your .env:
ENCRYPTED_ROUTE_PARAMS_CIPHER=compact
This uses AES-256-GCM + HKDF to produce significantly shorter tokens while maintaining high security.
JSON & Form Request Support
Decrypt parameters sent in request bodies automatically by adding the middleware. Supported content types: application/json, application/x-www-form-urlencoded, and multipart/form-data (file uploads are left unchanged). You can use the provided alias encrypted.json.request (once configured in config):
Route::post('/api/invoices', ...) ->middleware([\Imran\EncryptedRouteParams\Middleware\DecryptEncryptedJsonRequestParameters::class]);
Set decrypt_query_string to true to also run the same keys-strategy decryption against the query string (any HTTP method). Off by default, since query strings tend to end up in access logs and browser history more readily than request bodies.
Context Binding (defense in depth)
By default, an encrypted token is portable: a token minted for {invoice} on one route would also decrypt successfully if pasted into a differently-named parameter on another route, since nothing ties the ciphertext to where it was generated. Enable bind_context to close that off for path parameters:
ENCRYPTED_ROUTE_PARAMS_BIND_CONTEXT=true
With this on, route() / encrypted_route() bind each encrypted path parameter to its route name and parameter name, and the decrypt middleware requires an exact match โ a token minted for invoices.show's {invoice} parameter is rejected (404/400) if replayed against a different route or parameter. Tokens minted without a context (e.g. via the model trait or a bare encrypt_route_param($value) call) are unaffected and keep working.
You can opt individual JSON/form fields into the same protection by minting tokens with an explicit context and enabling bind_context:
// When building the token: $token = encrypt_route_param($documentId, 'field:docId'); // Decrypting manually: $id = decrypt_route_param($token, 'field:docId');
The DecryptEncryptedJsonRequestParameters middleware's keys strategy automatically expects field:{key} as the context for each configured key when bind_context is enabled.
Token Expiry
Set token_ttl (seconds) to make newly minted tokens stop decrypting after a fixed window:
ENCRYPTED_ROUTE_PARAMS_TOKEN_TTL=3600
Default is 0 (tokens never expire). This only affects tokens encrypted after the setting is applied.
Compact Cipher Key Rotation
The compact cipher derives its key from APP_KEY via HKDF, so rotating APP_KEY normally invalidates every outstanding compact token immediately. To roll over gracefully, list the previous APP_KEY value(s) (same format Laravel uses for APP_PREVIOUS_KEYS):
ENCRYPTED_ROUTE_PARAMS_COMPACT_PREVIOUS_KEYS=base64:oldKeyOne==,base64:oldKeyTwo==
The laravel cipher already benefits from Laravel's own APP_PREVIOUS_KEYS automatically.
Inspecting What's Encrypted
Since discovery can fail silently (for example, a model with a non-trivial constructor throwing during reflection), use the bundled command to see exactly which routes and parameters the package currently treats as encrypted:
php artisan route-params:list php artisan route-params:list --json
๐งช Helper Methods
The package provides several global helper methods for manual encryption/decryption:
encrypt_route_param(mixed $value)
Encrypt a scalar value using the package's configuration. Useful for manually building JSON responses or payloads.
return [ 'id' => encrypt_route_param($user->id), ];
decrypt_route_param(string $token)
Decrypt a token produced by the package.
$rawId = decrypt_route_param($request->input('encrypted_id'));
encrypted_route(string $name, array $parameters = [], bool $absolute = true)
Explicitly generate an encrypted route URL. This is useful if you have transparent_url_generation set to false.
$url = encrypted_route('users.show', ['user' => 1]);
๐ Configuration
Publish the config file:
php artisan vendor:publish --tag=encrypted-route-params-config
Options & Environment Variables
| Key | Environment Variable | Default | Description |
|---|---|---|---|
enabled |
ENCRYPTED_ROUTE_PARAMS_ENABLED |
true |
Master switch for the package. |
transparent_url_generation |
ENCRYPTED_ROUTE_PARAMS_TRANSPARENT_URL |
true |
If true, wraps Laravel's UrlGenerator so route() works automatically. |
cipher |
ENCRYPTED_ROUTE_PARAMS_CIPHER |
laravel |
laravel (standard) or compact (shorter tokens). |
compact_previous_keys |
ENCRYPTED_ROUTE_PARAMS_COMPACT_PREVIOUS_KEYS |
'' |
Comma-separated previous APP_KEY values, tried on decrypt after an APP_KEY rotation (compact cipher only). |
bind_context |
ENCRYPTED_ROUTE_PARAMS_BIND_CONTEXT |
false |
Binds path-parameter tokens to their route+parameter name, rejecting cross-route replay. |
token_ttl |
ENCRYPTED_ROUTE_PARAMS_TOKEN_TTL |
0 |
Seconds after which a newly minted token expires. 0 = never. |
failure |
ENCRYPTED_ROUTE_PARAMS_FAILURE |
404 |
Returns 404 or 400 on decryption failure. |
log_failures |
ENCRYPTED_ROUTE_PARAMS_LOG_FAILURES |
true |
Log decryption failures to your application logs. |
max_plaintext_length |
ENCRYPTED_ROUTE_PARAMS_MAX_PLAINTEXT_LENGTH |
4096 |
Max size of data before encryption. |
decrypt_json_strategy |
ENCRYPTED_ROUTE_PARAMS_JSON_STRATEGY |
keys |
keys (explicit keys) or all_strings (recursive search). |
decrypt_json_request_keys |
ENCRYPTED_ROUTE_PARAMS_JSON_KEYS |
'' |
Comma-separated list of keys to decrypt in JSON/form bodies (and the query string, if enabled). |
decrypt_json_min_token_length |
ENCRYPTED_ROUTE_PARAMS_JSON_MIN_TOKEN_LENGTH |
24 |
Minimum string length before all_strings mode attempts decryption. |
json_decrypt_strict |
ENCRYPTED_ROUTE_PARAMS_JSON_STRICT |
false |
If true, returns 400 on invalid JSON ciphertext. |
decrypt_query_string |
ENCRYPTED_ROUTE_PARAMS_DECRYPT_QUERY_STRING |
false |
If true, the keys strategy also runs against the query string. |
middleware |
ENCRYPTED_ROUTE_PARAMS_MIDDLEWARE |
encrypted.route.params |
Alias registered for route parameter decryption middleware. |
json_middleware |
ENCRYPTED_ROUTE_PARAMS_JSON_MIDDLEWARE |
'' |
Optional alias for JSON request decryption middleware. |
โค๏ธ Support the Project
This package is free and open-source. If you find it useful, please consider:
- Starring the repo on GitHub.
- Reporting bugs or suggesting features via Issues.
- Contributing code via Pull Requests.
๐ Sponsors
If your company uses this package in production, please consider sponsoring development to ensure its long-term maintenance. Contact silent.lips125@gmail.com for sponsorship opportunities.
๐ค Contributing
Please see CONTRIBUTING for details.
๐ Security
If you discover any security-related issues, please email silent.lips125@gmail.com instead of using the issue tracker.
๐ License
The MIT License (MIT). Please see License File for more information.