I am working on a Laravel 11 API project with Nuxt front end. And also we are using Passport for authentication and authorization. I am having trouble with Users. So let’s say there are just 2 users like below :
[
[
'first_name' => 'John',
'last_name' => 'Doe',
'email' => '[email protected]',
'country_id' => 1,
'phone' => '1112222222',
'isAdmin' => true,
'password' => Hash::make(Str::password(32, true, true, true, false))
],
[
'first_name' => 'Bob',
'last_name' => 'Carlo',
'email' => '[email protected]',
'country_id' => 1,
'phone' => '111111111',
'isAdmin' => true,
'password' => Hash::make(Str::password(32, true, true, true, false)),
'email_send_at' => null,
'phone_send_at' => null
],
];
Let’s say I am authenticated as Bob. I saved the Bearer token to Postman. And then lets say if i send a GET request to filter Users with lets say their full name with “Bob” query string I get the correct result which is just second user. And then let’s say i send another GET request with just “o” and both of them is returned which is also correct. But if a send a request with just “J” or any query string value that will return the other user i am not getting the result i wanted. Auth fails. It’s not just for FullName. Same problem exist for other fields. Lets say if i send a request with phone query string. if i send “1” the result must be both of them and it is. But if i send “2” it just not works as i wanted.
Firstly here is api.php
routes :
// User Routes - Start
Route::prefix('user')->group(function ()
Route::group([
"middleware" => ["auth:api"]
], function () {
Route::post('/', [UserController::class, 'store'])->middleware('can:publish user');
Route::put('/{user}', [UserController::class, 'update'])->middleware('can:update user');
Route::delete('/{user}', [UserController::class, 'destroy'])->middleware('can:destroy user');
Route::delete('/', [UserController::class, 'destroyMultiple'])->middleware('can:destroy user');
});
Route::group([
"middleware" => ["client"]
], function () {
Route::get('/', [UserController::class, 'index']);
Route::get('/{user}', [UserController::class, 'show']);
});
});
// User Routes - End
We are using Global Scope to apply search and filters to models. Here is User
model :
class User extends Authenticatable
{
use HasFactory, Notifiable, HasRoles, HasApiTokens;
protected static function boot()
{
parent::boot();
static::addGlobalScope(new FilterBy('AppServicesV1UserUserFilters', request()->all()));
}
//......
Here is FilterBy
Scope :
class FilterBy implements Scope
{
protected $namespace;
protected $filters;
public function __construct($namespace, $filters)
{
$this->namespace = $namespace;
$this->filters = $filters;
}
/**
* Apply the scope to a given Eloquent query builder.
*/
public function apply(Builder $builder, Model $model): void
{
if (request()->method() !== 'GET' || empty($this->filters)) {
return;
}
$filter = new FilterBuilder($builder, $this->filters, $this->namespace);
$builder = $filter->apply();
}
}
And also FilterBuilder
class :
class FilterBy implements Scope
{
protected $namespace;
protected $filters;
public function __construct($namespace, $filters)
{
$this->namespace = $namespace;
$this->filters = $filters;
}
/**
* Apply the scope to a given Eloquent query builder.
*/
public function apply(Builder $builder, Model $model): void
{
if (request()->method() !== 'GET' || empty($this->filters)) {
return;
}
$filter = new FilterBuilder($builder, $this->filters, $this->namespace);
$builder = $filter->apply();
}
}
So basically I have a User directory with the classes that are actually adding filters to query. For example here is FullName.php
:
class FullName extends QueryFilter implements FilterContract
{
protected $query;
public function __construct($query)
{
$this->query = $query;
}
public function handle($value = ""): void
{
$this->query->where(DB::raw("CONCAT(first_name, ' ', last_name)"), 'ilike', '%' . $value . '%');
}
}
Here is Controller method :
public function index(Request $request)
{
return new UserCollection(User::paginate($request->get('perPage', 15)));
}
Also here is the UserCollection
and UserResource
:
class UserCollection extends ResourceCollection
{
public function __construct($resource)
{
parent::__construct($resource);
$this->preserveAllQueryParameters = true;
}
public function toArray(Request $request): array
{
return parent::toArray($request);
}
}
class UserResource extends JsonResource
{
public function toArray(Request $request): array
{
if (Auth::check() && Auth::user()->isAdmin && Auth::user()->can('show user')) {
return [
'id' => $this->id,
'first_name' => $this->first_name,
'last_name' => $this->last_name,
'email' => $this->email,
'country_id' => $this->country_id,
'phone' => $this->phone,
'isAdmin' => $this->isAdmin,
];
}
return [
'first_name' => $this->first_name,
'last_name' => $this->last_name,
'country_id' => $this->country_id,
];
}
}
So as i mentioned if i send a request with query string that will return JUST one user which is not currently authenticated user it returns with just first name, last name and country id. I used dd(Auth::user())
at UserController index method. If i send a request that will cause the problem i mentioned it is ‘null’ if i send a request that will return both users it is not null.