Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

paulaomsg's avatar

How to Resolve a Conflict Between Sessions and Encrypted IDs in Laravel?

Hello friends,

I’m facing an issue here with Laravel 11.

I’m using these methods in some models:

public function getIdAttribute($value) {
    return encode($value);
}
public function resolveRouteBinding($value, $field = null) {
    return $this->where('id', decode($value))->firstOrFail();
}

It works perfectly when I want to encrypt the IDs in the routes, e.g., local/address/{hash}/edit.

However, when I add this to the User model and try to access the system at localhost, I get an error when updating the session (for now, I’m still using database sessions).

SQLSTATE[HY000]: General error: 1366 Incorrect integer value: 'VFZFOVBRPT0_E_' for column 'user_id' at row 1 (Connection: mysql, SQL: update `sessions` set `payload` = YTo0OntzOjY6Il90b2tlbiI7czo0MDoiYlhvUnM1TmpjTnQwOTVuZFFNeVRST0U1blYwd05vQlp1SmlWZGlEbyI7czo5OiJfcHJldmlvdXMiO2E6MTp7czozOiJ1cmwiO3M6Njc6Imh0dHA6Ly9sb2NhbGhvc3Q6ODAwMC9hZG0tdGVuYW50L2NvbmRvbWluaW9zL1ZGWkZPVkJSUFQwX0VfL3Blc3NvYXMiO31zOjY6Il9mbGFzaCI7YToyOntzOjM6Im9sZCI7YTowOnt9czozOiJuZXciO2E6MDp7fX1zOjUwOiJsb2dpbl93ZWJfNTliYTM2YWRkYzJiMmY5NDAxNTgwZjAxNGM3ZjU4ZWE0ZTMwOTg5ZCI7aToxO30=, `last_activity` = 1733187296, `user_id` = VFZFOVBRPT0_E_, `user_agent` = Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36 where `id` = COjFmDt3DZ2cUsa7UHUqS2I7jffcH9S2SNAl4fA2)

I believe this happens because Laravel doesn’t know that user_id is $user->id and that it’s automatically encrypted by the getIdAttribute. If the session had a model, I could just use a mutator for the user_id and it would work... but the session doesn’t have a model.

I have no idea how to fix this. Is it impossible? Can someone help me, please?

0 likes
8 replies
jlrdw's avatar

Does id need to be encrypted?

martinbean's avatar
Level 80

@paulaomsg Sure. No one is disputing that. But adding a getIdAttribute accessor to your model is not the way to achieve this, as that’s going to have knock-on effects anywhere the ID is requested—which you’ve found.

You should instead consider routing using a “pseudo” attribute:

Route::get('/users/{user:encrypted_id}', [UserController::class, 'show']);

You can then implement the resolveRouteBinding method in your model that looks for this attribute name (encrypted_id) and does the problem decrypting of the value and query:

class User extends Authenticatable
{
    public function resolveRouteBinding($value, $field = null)
    {
        if ($field === 'encrypted_id') {
            $id = decrypt_string($value);

            return static::query()->findOrFail($id);
        }

        return parent::resolveRouteBinding($value, $field);
    }
}

This will stop your application throwing exceptions when trying to use the value of $user->id.

Alternatively, you could just use something like Sqids (formerly known as Hashids):

Route::get('users/{user:sqid}', [UserController::class, 'show']);
class User extends Authenticatable
{
    public function getSqidAttribute(): string
    {
        $sqids = new Sqids();

        return $sqids->encode([$this->getKey()]);
    }

    public function resolveRouteBinding($value, $field = null)
    {
        if ($field === 'sqid') {
            $sqids = new Sqids();

            $ids = $sqids->decode($value);

            return static::query()->findOrFail($ids[0]);
        }

        return parent::resolveRouteBinding($value, $field);
    }
}

If you went this route, I’d wrap those two methods up in a HasSqid trait that you can then apply to multiple models you wish to support routing via Sqids. This way, you can just carry on generating routes as normal:

Route::get('users/{user:sqid]', [UserController::class, 'show'])->name('user.show');
<!-- Below will use 'sqid' attribute as defined in route -->
<a href="{{ route('user.show', compact('user')) }}">View user details</a>
1 like
paulaomsg's avatar

@martinbean Sensational, indeed a perfect approach! But first, I want to apologize to @jlrdw in case I didn’t understand him correctly. After martinbean’s explanation, it made me reflect on it…

Back to the solution… I wasn’t aware of these "pseudo" attributes, and now your approach makes total sense.

In my case, I used Sqids, and I thought it turned out more interesting.

Thank you all so much for your dedication and time in helping me.

1 like
paulaomsg's avatar

@martinbean This solution worked perfectly, but when adding a second parameter, it stopped working. Example:

Route::get('invoice/{user:sqid}/address/{address:sqid}', [UserController::class, 'show'])->name('user.show');

Of course, this route doesn’t make much sense, but just to illustrate.

It would return an error like:

Column not found: 1054 Unknown column 'address.sqid'

I spent the whole day trying to solve it... I added another name, e.g., sqid2, but nothing worked...

JussiMannisto's avatar

Why do you have this part:

public function getIdAttribute($value) {
    return encode($value);
}

Shouldn't the customized binding resolution be enough? If you overwrite the primary key accessor like this, you'll get errors for sure.

You should use a different name for the attribute, such as id_encrypted, then use that as the route parameter. If needed, you can hide the actual id on the client side by adding it to the $hidden array in the model class.

1 like
paulaomsg's avatar

@JussiMannisto

I understand, thank you for responding.

I would say practicality, since I have tools that do the job automatically, in a simpler way, and ensure greater interoperability, as they are native to Laravel. See, it seems very sensible to use:

route('example.edit', $model);

And then:

public function edit(Model $model) { ... }

My knowledge of Laravel is somewhat limited, but in my understanding, getIdAttribute() and resolveRouteBinding() are methods that we can use to solve these types of issues, since passing the ID without processing it in the URL isn't ideal.

Now... is there a more efficient native way to do this? I don't know...

I think creating a column in the database just to store an encrypted ID could have some side effects. It could slow down the query, and we'd have to ensure that id_encrypted is the same as ID, etc...

On the other hand, using id_encrypted just as a parameter could make the code repetitive and more complex.

For example:

route('example.edit', ['id_encrypted' => encode($model->id)]);

Then

public function edit(string $id_encrypted) {
    $model = Model::find(decode($id_encrypted));
}

Now, if there is a better way to do this, I would be very happy to learn.

Snapey's avatar

If you are going to pass these values in routes, you need to also ensure that they are valid for URL encoding

Personally, when I need to obfuscate the ID, I choose HashIDs

https://github.com/vinkla/laravel-hashids

And if I have a route that should work only for the authenticated user, then don't pass their own ID in the route

And above all else, don't substitute obfuscation for authorisation. You still need to check that the user is allowed to edit the mentioned resource.

Please or to participate in this conversation.