Jens Segers on

Easy id obfuscation with Laravel 5

Be sure to check out my new blog post about id obfuscation with Optimus.

Obfuscating ids in urls is a great way of hiding your application's internals from your visitors. It reduces the chance for malicious users to guess urls and prevents everyone from knowing how many user accounts or articles your website has.

For PHP, there's a cool library called hashids, which generates Youtube-like obfuscated ids; it converts an integer like 347 to yr8 and back again. So instead of writing our own obfuscation logic, let's include it in our project:

composer require hashids/hashids

Since Laravel 5, there's a new interface that is being implemented by the Eloquent models, called UrlRoutable. It tells the router instance, that it has specific methods to convert the model instance to a single value that should be used in urls. This means that you are now able to pass Eloquent objects as route parameters, and Laravel will automatically convert them for you.

By default, Laravel asks the model to return the primary key, but we can simply overwrite the getRouteKey method so that the id is obfuscated:

/**
 * Get the value of the model's route key.
 *
 * @return mixed
 */
public function getRouteKey()
{
    $hashids = new \Hashids\Hashids('MySecretSalt');

    return $hashids->encode($this->getKey());
}

Easy right? Now everywhere where you pass an instance of this model to a route or url, it will return the obfuscated id:

$user = User::find(123);
echo Url::route('users.show', [$user]);
> http://example.com/users/Mj3

So imagine we have the following route:

Route::get('users/{user}', ['as' => 'users.show', 'user' => 'UserController@show']);

As of now, the controller will still receive the obfuscated user id. But we can easily decode it back by adding this route binding:

Route::bind('user', function($value, $route)
{
    $hashids = new Hashids\Hashids('MySecretSalt');

    return $hashids->decode($value)[0];
});

This tells the router, that whenever we get passed a user parameter in the url or route, we should decode it before passing it to the controller.

Last year, I wrote a blog post about how I love route model binding. So let's take it a step further and pre-fetch the model instance:

Route::bind('user', function($value, $route)
{
    $hashids = new Hashids\Hashids('MySecretSalt');

    $id = $hashids->decode($value)[0];

    return User::findOrFail($id);
});

I hope you liked this example use case of the new UrlRoutable feature in Laravel 5. If you want me to go over some more features of Laravel 5, let me know in the comments below.

Webmentions

Tweet about this blog post and you will appear below!