laravel / framework

The Laravel Framework.
https://laravel.com
MIT License
32.5k stars 11.01k forks source link

auth()->guard('sanctum')->user() producing wrong user under specific condition #51257

Closed mjzavar closed 6 months ago

mjzavar commented 6 months ago

Laravel Version

11.5.0

PHP Version

8.2.12

Database Driver & Version

No response

Description

laravel sanctum is producing a different user from authenticated user when it's accessed via auth()->guard('sanctum')->user() in the model / without using middleware on the route

Steps To Reproduce

i have a controller which is accessible for both registered user and guests so im not using the auth middleware on the route , but still i need to access the user in my model relation if logged in , so im using auth()->guard('sanctum')->user()

this is my controller

function show(Project $project)
{
    $project->load('followingStatus');
     return new ProjectResource($project) ;
}

and this is the relation method in my model

public function followingStatus()
{
    return $this->morphOne(Fellowship::class, 'morphable')->where('fellowships.user_id',  auth()->guard('sanctum')->user()->id );
}

so after not getting the expected results i've outputted the queries and saw it's using a different user id from the authenticated in the relation query

function show(Project $project)
{
    DB::connection()->enableQueryLog();
    $project->load('followingStatus'   );
    $queries = DB::getQueryLog();
    $data[]  = ['queries' => $queries  , 'user' => auth()->guard('sanctum')->user() ];
    return $data ;
}

here is the output

[
    {
        "queries": [
            {
                "query": "select * from `personal_access_tokens` where `personal_access_tokens`.`id` = ? limit 1",
                "bindings": [
                    "2"
                ],
                "time": 0.49
            },
            {
                "query": "select * from `users` where `users`.`deleted_at` is null limit 1",
                "bindings": [],
                "time": 0.56
            },
            {
                "query": "update `personal_access_tokens` set `last_used_at` = ?, `personal_access_tokens`.`updated_at` = ? where `id` = ?",
                "bindings": [
                    "2024-04-30 21:16:38",
                    "2024-04-30 21:16:38",
                    2
                ],
                "time": 2.61
            },
            {
                "query": "select * from `fellowships` where `fellowships`.`user_id` = ? and `fellowships`.`morphable_id` in (40) and `fellowships`.`morphable_type` = ?",
                "bindings": [
                    1,
                    "App\\Models\\Project"
                ],
                "time": 0.58
            }
        ],
        "user": {
            "id": 1,
            "username": "a6caec07-ff64-11ee-b7b4-06ce00aeae8d",
            "first_name": "max",
            "last_name": "zak",
            "job_title": "cto",
            "email": "maxim@gmail.com",
            "email_verified_at": null,
            "completed_at": "2024-04-20 23:06:54",
            "followers": 0,
            "created_at": "2024-04-20T22:23:45.000000Z",
            "updated_at": "2024-04-20T23:06:54.000000Z",
            "stripe_id": null,
            "pm_type": null,
            "pm_last_four": null,
            "trial_ends_at": null
        }
    }
]

it's using user with id of 1 in the query and also in my user output after loading the relation , but my auth token belongs to user with id of 2 , i can even see the query reading personal_access_tokens with id of 2 in the first query which belongs to users.id of 2

also noticed if run the auth()->guard('sanctum')->user() before loading the relation it works fine and it will use the correct user id in the relation query , but for some reason when it's called inside the relation function it'll produce a different user

so i've updated the code to run auth before relation

function show(Project $project)
{
    DB::connection()->enableQueryLog();
    $data = [
               'user_before_relation' => auth()->guard('sanctum')->user(),
           ];

    $project->load('followingStatus'   );
    $queries = DB::getQueryLog();
        $data['after_relation']  = ['queries' => $queries  , 'user' => auth()->guard('sanctum')->user() ];
    return $data ;
}

and just like that everything works fine and i get user with id of 2 in all queries and outputs

{
        "user_before_relation": {
        "id": 2,
        "username": "48438961-ff66-11ee-b7b4-06ce00aeae8d",
        "first_name": "max",
        "last_name": "zak",
        "job_title": null,
        "email": "emp@gmail.com",
        "email_verified_at": null,
        "completed_at": null,
        "followers": 0,
        "created_at": "2024-04-20T22:35:25.000000Z",
        "updated_at": "2024-04-20T22:35:25.000000Z",
        "stripe_id": null,
        "pm_type": null,
        "pm_last_four": null,
        "trial_ends_at": null
    },
    "after_relation": {
        "queries": [
            {
                "query": "select * from `personal_access_tokens` where `personal_access_tokens`.`id` = ? limit 1",
                "bindings": [
                    "2"
                ],
                "time": 0.44
            },
            {
                "query": "select * from `users` where `users`.`id` = ? and `users`.`deleted_at` is null limit 1",
                "bindings": [
                    2
                ],
                "time": 0.64
            },
            {
                "query": "update `personal_access_tokens` set `last_used_at` = ?, `personal_access_tokens`.`updated_at` = ? where `id` = ?",
                "bindings": [
                    "2024-04-30 21:14:39",
                    "2024-04-30 21:14:39",
                    2
                ],
                "time": 3.67
            },
            {
                "query": "select * from `fellowships` where `fellowships`.`user_id` = ? and `fellowships`.`morphable_id` in (40) and `fellowships`.`morphable_type` = ?",
                "bindings": [
                    2,
                    "App\\Models\\Project"
                ],
                "time": 0.57
            }
        ],
        "user": {
            "id": 2,
            "username": "48438961-ff66-11ee-b7b4-06ce00aeae8d",
            "first_name": "max",
            "last_name": "zak",
            "job_title": null,
            "email": "emp@gmail.com",
            "email_verified_at": null,
            "completed_at": null,
            "followers": 0,
            "created_at": "2024-04-20T22:35:25.000000Z",
            "updated_at": "2024-04-20T22:35:25.000000Z",
            "stripe_id": null,
            "pm_type": null,
            "pm_last_four": null,
            "trial_ends_at": null
        }
    },
}
Muffinman commented 6 months ago

Sanctum will check each guard in your config('sanctum.guard') value in turn, until one provides a valid user. It will do this as soon as the guard is invoked.

If none return a valid user, then personal_access_tokens are queried by the data provided in the Bearer request header. That ID query in your example comes from here:

    /**
     * Find the token instance matching the given token.
     *
     * @param  string  $token
     * @return static|null
     */
    public static function findToken($token)
    {
        if (strpos($token, '|') === false) {
            return static::where('token', hash('sha256', $token))->first();
        }

        [$id, $token] = explode('|', $token, 2);

        if ($instance = static::find($id)) {
            return hash_equals($instance->token, hash('sha256', $token)) ? $instance : null;
        }
    }

I don't believe the code above should ever call a query like

select * from `users` where `users`.`id` = ? and `users`.`deleted_at` is null limit 1

so my hunch is that you have customised the PeronsalAccessToken model and have other queries happening?

I think you would need to put this into a reproduction repo.

crynobone commented 6 months ago

Hey there, thanks for reporting this issue.

We'll need more info and/or code to debug this further. Can you please create a repository with the command below, commit the code that reproduces the issue as one separate commit on the main/master branch and share the repository here?

Please make sure that you have the latest version of the Laravel installer in order to run this command. Please also make sure you have both Git & the GitHub CLI tool properly set up.

laravel new bug-report --github="--public"

Do not amend and create a separate commit with your custom changes. After you've posted the repository, we'll try to reproduce the issue.

Thanks!

driesvints commented 6 months ago

Closing this issue because it's inactive, already solved, old or not relevant anymore. Feel to open up a new issue if you're still experiencing this.