Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Default auth guard is wrong for Laravel 11, config not updating #2729

Open
sts-ryan-holton opened this issue Sep 30, 2024 · 7 comments
Open

Comments

@sts-ryan-holton
Copy link

sts-ryan-holton commented Sep 30, 2024

Description

I've just upgraded my Laravel project to Laravel 11 from Laravel 10. I've flushed caches. I'm getting the error:

There is no role named super_admin for guard web.

Coming from within the RoleDoesNotExist class line 11.

My User model defines HasRoles, and my default auth gaurd in my config file is api, not web. I haven't made any changes to my User model. Someone suggests this but in my project, I wasn't defining this in Laravel 10. So I think this is a bug.

Steps To Reproduce

Myauth.php config:

/*
|--------------------------------------------------------------------------
| Authentication Defaults
|--------------------------------------------------------------------------
|
| This option defines the default authentication "guard" and password
| reset "broker" for your application. You may change these values
| as required, but they're a perfect start for most applications.
|
*/

'defaults' => [
    'guard' => env('AUTH_GUARD', 'api'),
    'passwords' => env('AUTH_PASSWORD_BROKER', 'users'),
],

/*
|--------------------------------------------------------------------------
| Authentication Guards
|--------------------------------------------------------------------------
|
| Next, you may define every authentication guard for your application.
| Of course, a great default configuration has been defined for you
| which utilizes session storage plus the Eloquent user provider.
|
| All authentication guards have a user provider, which defines how the
| users are actually retrieved out of your database or other storage
| system used by the application. Typically, Eloquent is utilized.
|
| Supported: "session"
|
*/

'guards' => [
    'api' => [
        'driver' => 'session',
        'provider' => 'users',
    ],

    'web' => [
        'driver' => 'session',
        'provider' => 'users',
    ]
],

Example Application

No response

Version of spatie/laravel-permission package:

6.4.0

Version of laravel/framework package:

11.25

PHP version:

8.3.9

Database engine and version:

MySQL 8

OS: Windows/Mac/Linux version:

Mac

Additional context

My project uses Laravel Sanctum and is an API to a Nuxt front-end. The following in your code:

/**
 * Lookup a guard name relevant for the $class model and the current user.
 *
 * @param  string|Model  $class  model class object or name
 * @return string guard name
 */
public static function getDefaultName($class): string
{
    $default = config('auth.defaults.guard');

    $possible_guards = static::getNames($class);

    // return current-detected auth.defaults.guard if it matches one of those that have been checked
    if ($possible_guards->contains($default)) {
        return $default;
    }

    return $possible_guards->first() ?: $default;
}

$possible_guards appears to return ['web', 'api'] in this order. But changing them around in my config doesn't work. They're always in this order. In addition, my default is sanctum.

@drbyte
Copy link
Collaborator

drbyte commented Sep 30, 2024

$possible_guards appears to return ['web', 'api'] in this order.

If $possible_guards is not a single value, then it is coming from getConfigAuthGuards():

/**
* Get list of relevant guards for the $class model based on config(auth) settings.
*
* Lookup flow:
* - get names of models for guards defined in auth.guards where a provider is set
* - filter for provider models matching the model $class being checked (important for Lumen)
* - keys() gives just the names of the matched guards
* - return collection of guard names
*/
protected static function getConfigAuthGuards(string $class): Collection
{
return collect(config('auth.guards'))
->map(fn ($guard) => isset($guard['provider']) ? config("auth.providers.{$guard['provider']}.model") : null)
->filter(fn ($model) => $class === $model)
->keys();
}

... which uses collection methods to query and filter (not sort) values coming back from config('auth.guards').

Is your config('auth.guards') being altered by the application anywhere? Or is config('auth.providers') causing an unexpected side-effect when filtering is applied (intended only to rule-out guards for which there is no matching provider, especially to avoid crashes in Lumen)?

@sts-ryan-holton
Copy link
Author

@drbyte All I did was upgrade to Laravel 11 here. The config files between the two are the same. The only thing I can think of is that internally Laravel 11 merges default config with custom ones. But I don't understand why the permissions package is causing a difference here.

Laravel 11 auth.php config file:

AUTH_GUARD is set to api

<?php

return [

    /*
    |--------------------------------------------------------------------------
    | Authentication Defaults
    |--------------------------------------------------------------------------
    |
    | This option defines the default authentication "guard" and password
    | reset "broker" for your application. You may change these values
    | as required, but they're a perfect start for most applications.
    |
    */

    'defaults' => [
        'guard' => env('AUTH_GUARD', 'api'),
        'passwords' => env('AUTH_PASSWORD_BROKER', 'users'),
    ],

    /*
    |--------------------------------------------------------------------------
    | Authentication Guards
    |--------------------------------------------------------------------------
    |
    | Next, you may define every authentication guard for your application.
    | Of course, a great default configuration has been defined for you
    | which utilizes session storage plus the Eloquent user provider.
    |
    | All authentication guards have a user provider, which defines how the
    | users are actually retrieved out of your database or other storage
    | system used by the application. Typically, Eloquent is utilized.
    |
    | Supported: "session"
    |
    */

    'guards' => [
        'api' => [
            'driver' => 'session',
            'provider' => 'users',
        ],

        'web' => [
            'driver' => 'session',
            'provider' => 'users',
        ]
    ],

    /*
    |--------------------------------------------------------------------------
    | User Providers
    |--------------------------------------------------------------------------
    |
    | All authentication guards have a user provider, which defines how the
    | users are actually retrieved out of your database or other storage
    | system used by the application. Typically, Eloquent is utilized.
    |
    | If you have multiple user tables or models you may configure multiple
    | providers to represent the model / table. These providers may then
    | be assigned to any extra authentication guards you have defined.
    |
    | Supported: "database", "eloquent"
    |
    */

    'providers' => [
        'users' => [
            'driver' => 'eloquent',
            'model' => env('AUTH_MODEL', App\Models\User::class),
        ],

        // 'users' => [
        //     'driver' => 'database',
        //     'table' => 'users',
        // ],
    ],

    /*
    |--------------------------------------------------------------------------
    | Resetting Passwords
    |--------------------------------------------------------------------------
    |
    | These configuration options specify the behavior of Laravel's password
    | reset functionality, including the table utilized for token storage
    | and the user provider that is invoked to actually retrieve users.
    |
    | The expiry time is the number of minutes that each reset token will be
    | considered valid. This security feature keeps tokens short-lived so
    | they have less time to be guessed. You may change this as needed.
    |
    | The throttle setting is the number of seconds a user must wait before
    | generating more password reset tokens. This prevents the user from
    | quickly generating a very large amount of password reset tokens.
    |
    */

    'passwords' => [
        'users' => [
            'provider' => 'users',
            'table' => env('AUTH_PASSWORD_RESET_TOKEN_TABLE', 'password_reset_tokens'),
            'expire' => 60,
            'throttle' => 60,
        ],
    ],

    /*
    |--------------------------------------------------------------------------
    | Password Confirmation Timeout
    |--------------------------------------------------------------------------
    |
    | Here you may define the amount of seconds before a password confirmation
    | window expires and users are asked to re-enter their password via the
    | confirmation screen. By default, the timeout lasts for three hours.
    |
    */

    'password_timeout' => env('AUTH_PASSWORD_TIMEOUT', 10800),

];

Laravel 10 auth.php config file:

<?php

return [

    /*
    |--------------------------------------------------------------------------
    | Authentication Defaults
    |--------------------------------------------------------------------------
    |
    | This option controls the default authentication "guard" and password
    | reset options for your application. You may change these defaults
    | as required, but they're a perfect start for most applications.
    |
    */

    'defaults' => [
        'guard' => 'api',
        'passwords' => 'users',
    ],

    /*
    |--------------------------------------------------------------------------
    | Authentication Guards
    |--------------------------------------------------------------------------
    |
    | Next, you may define every authentication guard for your application.
    | Of course, a great default configuration has been defined for you
    | here which uses session storage and the Eloquent user provider.
    |
    | All authentication drivers have a user provider. This defines how the
    | users are actually retrieved out of your database or other storage
    | mechanisms used by this application to persist your user's data.
    |
    | Supported: "session"
    |
    */

    'guards' => [
        'api' => [
            'driver' => 'session',
            'provider' => 'users',
        ],

        'web' => [
            'driver' => 'session',
            'provider' => 'users',
        ]
    ],

    /*
    |--------------------------------------------------------------------------
    | User Providers
    |--------------------------------------------------------------------------
    |
    | All authentication drivers have a user provider. This defines how the
    | users are actually retrieved out of your database or other storage
    | mechanisms used by this application to persist your user's data.
    |
    | If you have multiple user tables or models you may configure multiple
    | sources which represent each model / table. These sources may then
    | be assigned to any extra authentication guards you have defined.
    |
    | Supported: "database", "eloquent"
    |
    */

    'providers' => [
        'users' => [
            'driver' => 'eloquent',
            'model' => App\Models\User::class,
        ],

        // 'users' => [
        //     'driver' => 'database',
        //     'table' => 'users',
        // ],
    ],

    /*
    |--------------------------------------------------------------------------
    | Resetting Passwords
    |--------------------------------------------------------------------------
    |
    | You may specify multiple password reset configurations if you have more
    | than one user table or model in the application and you want to have
    | separate password reset settings based on the specific user types.
    |
    | The expire time is the number of minutes that each reset token will be
    | considered valid. This security feature keeps tokens short-lived so
    | they have less time to be guessed. You may change this as needed.
    |
    | The throttle setting is the number of seconds a user must wait before
    | generating more password reset tokens. This prevents the user from
    | quickly generating a very large amount of password reset tokens.
    |
    */

    'passwords' => [
        'users' => [
            'provider' => 'users',
            'table' => 'password_resets',
            'expire' => 60,
            'throttle' => 60,
        ],
    ],

    /*
    |--------------------------------------------------------------------------
    | Password Confirmation Timeout
    |--------------------------------------------------------------------------
    |
    | Here you may define the amount of seconds before a password confirmation
    | times out and the user is prompted to re-enter their password via the
    | confirmation screen. By default, the timeout lasts for three hours.
    |
    */

    'password_timeout' => 3600,

];

@spatie spatie deleted a comment Oct 8, 2024
@spatie spatie deleted a comment Oct 9, 2024
@Raptor-PL
Copy link

Raptor-PL commented Oct 17, 2024

Hi,

I'm experiencing similar problem. I use Vue 3/Axios and Laravel 11/Sanctum as API with basic Permissions setup (no custom changes except those from docs to make it work).
When I seed my permissions and roles everything works fine and they have 'web' guard in DB.
Any role added via UI has 'sanctum' guard there.
When I tried to enforce 'sanctum' guard Permission::create(['name' => 'read permissions', 'guard_name'=>'sanctum']); while seeding I got:
Image

This might link to another issue when I try to add/sync permissions with newly created role:

public function store(Request $request)
    {
        $request->validate([
            'name' => 'required|string|max:255|min:5|unique:roles',
            'description' => 'nullable|string',
            'permissions' => 'nullable|array'
        ]);

        $role = Role::create($request->only(['name', 'description']));

        $permissionsArray = $request->only(['permissions']);
        
        $permissionsArray = array_map('intval', $permissionsArray); //added as per Permissions docs

        if($role && $permissionsArray) {
            $role->syncPermissions($permissionsArray);
        }
    }

I get:
Image
If we assume string/integer $id issue has been solved why are permissions looking 'sanctum' guard and not 'web'.
I'm not proficient with PHP and too afraid to tamper with the package in hope to improve it.
How can it be overcome?

I also get "Class name must be a valid object or a string" when trying to delete a role which makes me think all them problems might be related.

public function destroy(string $id)
    {

        $id= intval($id);
        if($id > 3) {
            
            $role = Role::findById($id);

            $role->delete();

            return response()->json(['message'=>'Resource deleted.'], 204);
        } else {
            return response()->json([
                'errors'=> 'This resource cannot be deleted.'
            ], 500);
        }
    }

I had no issue with Permissions package when used with Laravel 9 with the same limited PHP knowledge.

@codebyray
Copy link

codebyray commented Oct 28, 2024

@Raptor-PL I had a similar issue with using Jetstream package.
It appears when using the middleware auth:sanctum in the middleware for my Routes, it is throwing the error you're getting. To solve this I changed the middleware call to:

'auth:web,sanctum'

If you do not need to use sanctum specifically, you can remove it and just use 'auth' and it will work as expected.

By adding web it solved the issue and has not affected anywhere in teh app where I am checking for permissions. Hope this helps.

@drbyte
Copy link
Collaborator

drbyte commented Nov 8, 2024

@erikn69 I've been trying to find time to dig into this in Laravel 11 specifically, but have been out of town all this week again.

I'm wondering if there's something obscure that our tests aren't catching? Maybe some additional angles we need to test for?

@erikn69
Copy link
Contributor

erikn69 commented Nov 8, 2024

The only thing I can think of is that internally Laravel 11 merges default config with custom ones. But I don't understand why the permissions package is causing a difference here.

Yes, it does, since [11.x] Slim skeleton: Here: Illuminate/Foundation/Bootstrap/LoadConfiguration.php#L99

I could try to fix it in Laravel, but I'm sure they wouldn't accept the change.

Meanwhile, you could use dontMergeFrameworkConfiguration().

@alturic
Copy link

alturic commented Nov 30, 2024

Is this still an issue with Laravel 11/Jetstream? I'm trying to use a basic Gate::before() in my AppServiceProvider and it seems like it is never called.

Gate::before(function (User $user, string $ability) {
 dd('Inside gate');
});
dd('After gate');

"After gate" is all that is ever output.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

6 participants