Using Sanctum with Laravel Spark conflict - laravel

My setup
Laravel 8
Laravel Spark Mollie
I'm constantly hitting a brick wall when calling API requests with Spark & Sanctum. I've installed Sanctum with no problem and migrated.
I've added use Laravel\Sanctum\HasApiTokens; to app/Models/User.php and added use HasApiTokens; to the class.
My Api.php route
Route::group([
'middleware' => 'auth:sanctum'
], function () {
Route::get('categories', [\App\Http\Controllers\categories::class, 'fetchCategories']);
});
When I call the Api I get this error
ErrorException
Declaration of Laravel\Sanctum\HasApiTokens::tokenCan(string $ability) should be compatible with Laravel\Spark\User::tokenCan($ability)
I've tried changing use Laravel\Sanctum\HasApiTokens; to Laravel\Spark\HasApiTokens on User.php. The error goes away, but whenever I try calling the Api, it returns me back to the login homepage.
Any ideas? As the Spark documentation doesn't really explain how Sanctum or Api protection work.

The problem is that your main User class extends the User class from the vendor Spark library. This User model uses the trait named HasApiTokens which is not the same as Sanctum
Since you don't want to change the file from the vendor directory, one fix I found was to copy the original SparkUser model class from the vendor and create a new one like this and remove the trait HasApiTokens since you don't want to use it anymore.
<?php
namespace App\Models\Users;
use Illuminate\Support\Str;
use Laravel\Spark\Billable;
use Illuminate\Notifications\Notifiable;
use Illuminate\Foundation\Auth\User as Authenticatable;
class SparkUser extends Authenticatable
{
use Billable, Notifiable; // HasApiTokens was removed from the original SparkUser class
/**
* Get the profile photo URL attribute.
*
* #param string|null $value
* #return string|null
*/
public function getPhotoUrlAttribute($value)
{
return empty($value) ? 'https://www.gravatar.com/avatar/'.md5(Str::lower($this->email)).'.jpg?s=200&d=mm' : url($value);
}
/**
* Make the team user visible for the current user.
*
* #return $this
*/
public function shouldHaveSelfVisibility()
{
return $this->makeVisible([
'uses_two_factor_auth',
'country_code',
'phone',
'card_brand',
'card_last_four',
'card_country',
'billing_address',
'billing_address_line_2',
'billing_city',
'billing_state',
'billing_zip',
'billing_country',
'extra_billing_information'
]);
}
/**
* Convert the model instance to an array.
*
* #return array
*/
public function toArray()
{
$array = parent::toArray();
if (! in_array('tax_rate', $this->hidden)) {
$array['tax_rate'] = $this->taxPercentage();
}
return $array;
}
}
And now all I had to change was my original User class model to use this new model like this and add the trait HasApiTokens from Sanctum!
use App\Models\SparkUser; // Modified from the original in the vendor folder
use Laravel\Sanctum\HasApiTokens;
class User extends SparkUser
{
use HasApiTokens;
...
}

Related

Laravel Spatie Permissions - how to change default model 'role' by another model

please how can i change the default model 'role' by another model in my project (laravel project) has the same architecture as 'role'; the default model package.
in the config/permission.php config file contains:
'models' => [
/*
* When using the "HasPermissions" trait from this package, we need to know which
* Eloquent model should be used to retrieve your permissions. Of course, it
* is often just the "Permission" model but you may use whatever you like.
*
* The model you want to use as a Permission model needs to implement the
* `Spatie\Permission\Contracts\Permission` contract.
*/
'permission' => Spatie\Permission\Models\Permission::class,
/*
* When using the "HasRoles" trait from this package, we need to know which
* Eloquent model should be used to retrieve your roles. Of course, it
* is often just the "Role" model but you may use whatever you like.
*
* The model you want to use as a Role model needs to implement the
* `Spatie\Permission\Contracts\Role` contract.
*/
'role' => Spatie\Permission\Models\Role::class,
],
i want to have something like this:
'role' => App\Fonction::class,
the documentation says that i have to implement the
Spatie\Permission\Contracts\Role` contract.
any idea how can i do this in the right way.
the Fonction Model :
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Fonction extends Model
{
protected $fillable = [
'nom','description'
];
public function comptes()
{
return $this->hasMany('App\Compte') ;
}
}
You can extend Spatie\Permission\Models\Role class. It already implements Spatie\Permission\Contracts\Role interface. Check details in doc.
<?php
namespace App;
use Spatie\Permission\Models\Role;
class Fonction extends Role
{
protected $fillable = [
'nom','description'
];
public function comptes()
{
return $this->hasMany('App\Compte') ;
}
}

Extended User Model in a Package

Could you help me understand the right way to extend existing models? I'm developing a package and want to do as much as possible separated from the main application.
I want to have all existing functionality of the User model, but only add a relation to another model. In my package User I can have several (hasMany) Article. Instead of adding a method to \App\User I created a new model in my package class User extends \App\User:
namespace Package\Sample;
use Laravel\Passport\HasApiTokens;
use Illuminate\Notifications\Notifiable;
/**
* Class User
* #package Package\Sample
*/
class User extends \App\User
{
use HasApiTokens, Notifiable;
public function articles()
{
return $this->hasMany(Article::class);
}
}
To make it work I add the configuration for my package auth.php:
'providers' => [
'users' => [
'driver' => 'eloquent',
'model' => \Package\Sample\User::class,
],
],
'guards' => [
'api' => [
'driver' => 'passport',
'provider' => 'users',
],
],
My question: Is it a "best practice" to extend functionality of existing models?
I solved it by using the class_alias() php function.
In my package service provider I setup an alias of the model class defined for Authentication in /config/auth.php like this:
public function boot(){
class_alias(config("auth.providers.users.model"), 'ParentModel');
}
then I use ParentModel class where needed:
use ParentModel;
class Agent extends ParentModel {
...
}
Hope It makes sense for someone
that will prevent edits on the \Package\Sample\User::class. any new method needed will result in a package update.
why not declare a trait in your package containing your methods and use them in the App\User::class like what laravel is using.
namespace App;
use Illuminate\Notifications\Notifiable;
use Illuminate\Foundation\Auth\User as Authenticatable;
class User extends Authenticatable
{
use Notifiable;
I tried using the class_alias and although it works for basic usage, when things got more complicated my user class couldn't cut it. For example, my notifications where using the local package user type and not showing in app.
After doing more research I found using a trait IS the proper way like others have mentioned.
I found a comprehensive guide here: https://laravelpackage.com/08-models-and-migrations.html#approach-2-using-a-polymorphic-relationship
the gist:
Create a trait in your package:
Important here you can setup whatever the relationship you need depending on you db modeling.
// 'src/Traits/HasPosts.php'
<?php
namespace JohnDoe\BlogPackage\Traits;
use JohnDoe\BlogPackage\Models\Post;
trait HasPosts
{
public function posts()
{
return $this->morphMany(Post::class, 'author');
}
}
Add the use in whatever user classes it applies to in your app
// 'App\Models\User.php'
<?php
namespace App\Models;
use JohnDoe\BlogPackage\Traits\HasPosts;
class User extends Authenticatable
{
use HasPosts;
...
Lastly, you'll need to add an extra field in the db for the user_type where you are using the user_id.
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddUserTypeToPostTable extends Migration
{
/**
* Run the migrations.
*
* #return void
*/
public function up()
{
Schema::table('posts', function (Blueprint $table) {
$table->tinyText('user_type')
->comment('User class type')
->nullable();
});
}
/**
* Reverse the migrations.
*
* #return void
*/
public function down()
{
Schema::table('posts', function (Blueprint $table) {
$table->dropColumn('user_type');
});
}
}
You need to Add config(['auth.providers.users.model' => Myname\Myproject\App\Models\User::class]); to the boot-method inside my package-service-provider.
And Create new Class in your package.
namespace Myname\Myproject\App\Models;
class User extends \App\User
{
public function roles(){
return $this->belongsToMany('Myname\Myproject\App\Models\Role', 'user_role', 'user_id', 'role_id');
}
}

Separating methods into many controllers?

I noticed that many of the examples in the Laravel docs seem to have Controllers where the class has only one use/method.
For example, in this part of the doc, they have a UpdatePasswordController class with a single method, update():
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use App\Http\Controllers\Controller;
class UpdatePasswordController extends Controller
{
/**
* Update the password for the user.
*
* #param Request $request
* #return Response
*/
public function update(Request $request)
{
// Validate the new password length...
$request->user()->fill([
'password' => Hash::make($request->newPassword)
])->save();
}
}
Normally, I would put a method called updatePassword() in my UserController class (along with signIn(), signUp(), resetPassword(), etc.), but I'm wondering if it's better to create multiple classes, each with a single action?
Normally class is defined for a single purpose. In laravel , for authentication there is a Illuminate base bundle which is optimized for years.
As an example UpdatePasswordController only responsible for updating password,
AuthController only responsible for authentication.
I prefer you to reserch some MVC best practices

Laravel ResetsPasswords Trait

Locally, I have updated this trait to do some different redirecting after the user submits the getEmail() method to request the reset password link. When pushed to production, my editions aren't there. I'm guessing this is because the ResetsPasswords trait is in the laravel framework which is installed separately from my repository on the server.
If this is the case, what's the best way to change how this ResetsPasswords trait functions. Do I make my own and include that in the repository and just change my controller? Below is the PasswordController.
Thanks!
<?php
namespace App\Http\Controllers;
use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\ResetsPasswords;
class PasswordController extends Controller
{
/*
|--------------------------------------------------------------------------
| Password Reset Controller
|--------------------------------------------------------------------------
|
| This controller is responsible for handling password reset requests
| and uses a simple trait to include this behavior. You're free to
| explore this trait and override any methods you wish to tweak.
|
*/
use ResetsPasswords;
protected $redirectPath = '/main';
/**
* Create a new password controller instance.
*
* #return void
*/
public function __construct()
{
$this->middleware('guest');
}
}
Update: So, in the ResetsPasswords trait, I modify the redirect getSendResetLinkEmailSuccessResponse() method. So, do I instead just put that method in my controller (with the same name) and my edited code?
protected function getSendResetLinkEmailSuccessResponse($response)
{
...modified code...
}
You should not be making changes to the Laravel vendor files for the reason you stated.
Instead, you should override any of the trait functions you need to modify in your controller.
So just add the method to your controller with your modified code like so:
<?php
namespace App\Http\Controllers;
use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\ResetsPasswords;
class PasswordController extends Controller {
use ResetsPasswords;
protected $redirectPath = '/main';
public function __construct() {
$this->middleware('guest');
}
protected function getSendResetLinkEmailSuccessResponse($response) {
// modified code that sends an awesome flash message
}
}
Also, if all you're trying to do is change where the user is redirected to then you don't have to override the functions at all. All you have to do is change the redirectPath property in your controller.

using auth in laravel service provider

whenever i tried to use \Auth::User() i am getting non object property because my Auth::guest() returns true whenever i use them in service provider
use Illuminate\Contracts\View\View;
use Illuminate\Support\ServiceProvider;
use Illuminate\Contracts\View\Factory;
use App\relations;
use App\User;
use DB;
use Illuminate\Support\Facades\Auth;
class RelationServiceProvider extends ServiceProvider
{
/**
* Bootstrap the application services.
*
* #return void
*/
public function boot()
{
//
\Auth::User()->id;
$relation_friend_for_logged_user = DB::select( DB::raw("SELECT * FROM users"));
$value = "asd";
// for injecting objct
View()->share('count', $value);
}
but why \Auth::guest() is returning true whether i am logged in
You probably want to use a View Composer for this. As far as I know the authenticated user is not yet available in your service providers boot method.
public function boot(Guard $auth) {
view()->composer('*', function($view) use ($auth) {
// get the current user
$currentUser = $auth->user();
// do stuff with the current user
// ...
// pass the data to the view
$view->with('currentUser', $currentUser);
});
}
Code modified from https://laracasts.com/discuss/channels/general-discussion/how-do-i-get-the-current-authenticated-user-laravel-5

Resources