Does id need to be encrypted?
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?
@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>
Please or to participate in this conversation.