Do not expose database ids in your URLs
Tags: webdev, tutorial, security, php
Published: 2021-08-22 18:36:45

---
title: Do not expose database ids in your URLs
published: true
description: Learn how to harden
tags: webdev, tutorial, security, php
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/spiptsqr2ngwijd4107k.png
---

When developing web apps, we often rely on fetching informations from the database. Frameworks offer a way to make this easy thanks to [ORMs](https://en.wikipedia.org/wiki/Object%E2%80%93relational_mapping).

Most of the time, the ORM will find your model using the primary key as a reliable identifier. On the vast majority, primary keys are auto incremented integers.

Your URLs then look like this:

```
https://example.com/cart/12
https://example.com/user/15/post/41
...
```

Providing an incorrect or faulty authorization layer can create data leaks: users become able to navigate from data to data, which is something we would not want to allow if it is about sensitive data like users personal info.

## Obfuscating the identifier

An easy way to mitigate this security breach is to use a key that is:

- Hard to predict
- Random enough to be able to create a lot of items while keeping the unicity between them
- Easy to generate from your code and the database
- Checkable (we can know by analyzing its integrity if it is valid or not)

Thanks for us, [UUIDs](https://en.wikipedia.org/wiki/Universally_unique_identifier) are a very good candidate for it. It checks all the points above, and are very easy to use thanks to a wide range of package ready for use.

Your URLS now become harder to predict, which mitigate any developers mistake regarding authorization policies in your app:

```
https://example.com/cart/f6e4208f-5df4-466e-9225-01f296e2a09c
https://example.com/user/b1b44b12-34bc-4ed7-a666-9657b8b8c31b/post/e530d034-42f7-467b-91e3-1cc9313312eb
```

## Example on a Laravel app

In order to practice developing a [Laravel](https://laravel.com/) package, and using my first ever [Github Workflow](https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions), I created a package to make this job a breeze.

Here is how you can use [khalyomede/laravel-eloquent-uuid-slug](https://github.com/khalyomede/laravel-eloquent-uuid-slug#compatibility-table) in your app now.

### Install the package

First, head in your console, and type this command:

```bash
composer require khalyomede/laravel-eloquent-uuid-slug
```

### Add your slug column to your migration

Then, go to the [migration](https://laravel.com/docs/8.x/migrations) of the [model](https://laravel.com/docs/8.x/eloquent) of your choice, or create a new one if it has already been installed, and add the slug column.

```php
namespace Database\Migrations;

use App\Models\Cart;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

final class AddSlugColumnToCartTable extends Migration {
  public function up(): void
  {
    Schema::table('carts', function (Blueprint $table): void {
      Cart::addSlugColumn($table);
    });
  }

  public function down(): void
  {
    Schema::table('carts', function (Blueprint $table): void {
      Cart::dropSlugColumn($table); // available soon in v0.2.0
    });
  }
};
```

### Add the trait to your model

This is the last step, which will help configure how your model is retreived in your routes using [Route model binding](https://laravel.com/docs/8.x/routing#route-model-binding).

```php
namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Khalyomede\EloquentUuidSlug\Sluggable;

final class Cart extends Model
{
  use Sluggable;
}
```

### Use it in your controller

Now you are ready to take advantage of the package. The great thing with it is that your code does not change! You can keep using the `route()` method like you are used to.

```php
// routes/web.php

use App\Models\Cart;
use Illuminate\Http\RedirectResponse
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
use Illuminate\View\View;

Route::get("cart/{cart}", function (Cart $cart): View {
  return view("cart.show", [
    "cart" => $cart,
    "saveCartRoute" => route("cart.store", $cart),
  ]);
})->name("cart.show");

Route::post("cart/{cart}", function (Request $request, Cart $cart): RedirectResponse {
  $cart->update($request->only(["name"]));

  return redirect()->route("cart.show", $cart);
})->name("cart.store");
```

And voilà! This package will no interfer with your existing logic as you can see. The only thing that changes is now your routes are not exposed.

```php
route("cart.show", $cart); // https://example.com/cart/398e76a7-7c16-467c-93a8-04c06c6df703
```

## Conclusion

While this solution is not a magic way to resolve the initial problem of data leak, I find it is a very easy actionable mecanism to reduce the possibilities for malicious users to trick your system.

This does not prevent you to add an authorization or guard mecanism, like [Laravel Policies](https://laravel.com/docs/8.x/authorization#creating-policies) for example. For example, if a user navigates to a cart that have not been created by him/her, should not be able to view it.

Other folks here already talked about this subject, so make sure to give it a go if you want to read more about using UUIDs:

{% post rahulbanerjee99/4-packages-to-generate-uuids-in-javascript-lc1 %}

{% post jeroenvanrensen/why-you-shouldn-t-expose-your-incrementing-ids-57lc %}

{% post jeroenvanrensen/how-to-use-uuids-in-urls-in-laravel-1ja7 %}

Happy URL hardening!