Search code examples
phplaravellaravel-sanctumsanctum

Laravel Sanctum with uuid column in User model doesn't save tokenable_id


I'm try to use Laravel 8.x and Laravel sanctum 2.14.2 to authenticate my API and UUIDs as the primary key for my User model.

My custom PersonalAccessToken model

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Laravel\Sanctum\PersonalAccessToken as SanctumPersonalAccessToken;

class PersonalAccessToken extends SanctumPersonalAccessToken
{
    use HasFactory;

    protected $table = 'personal_access_tokens';

    public function tokenable()
    {
        return $this->morphTo('tokenable', "tokenable_type", "tokenable_id", "uuid");
    }
}

My personal_access_tokens migration schema

...
    public function up()
    {
        Schema::dropIfExists('personal_access_tokens');

        Schema::create('personal_access_tokens', function (Blueprint $table) {
            $table->id();
            $table->uuidMorphs('tokenable');
            $table->string('name');
            $table->string('token', 64)->unique();
            $table->text('abilities')->nullable();
            $table->timestamp('last_used_at')->nullable();
            $table->timestamps();
        });
    }
...

My AppServiceProvider

...
use App\Models\PersonalAccessToken;
use Illuminate\Support\Facades\URL;
use Illuminate\Support\ServiceProvider;
use Laravel\Sanctum\Sanctum;

class AppServiceProvider extends ServiceProvider
{
    /**
     * Register any application services.
     *
     * @return void
     */
    public function register()
    {
        Sanctum::ignoreMigrations();
    }

    /**
     * Bootstrap any application services.
     *
     * @return void
     */
    public function boot()
    {
        if($this->app->environment('production')) {
            URL::forceScheme('https');
        }

        Sanctum::usePersonalAccessTokenModel(PersonalAccessToken::class);
    }
}

When I try to get the token with $user->createToken($user->email)->plainTextToken, I get this error:

SQLSTATE[23000]: Integrity constraint violation: 1048 Column 'tokenable_id' cannot be null (SQL: insert into `personal_access_tokens` (`name`, `token`, `abilities`, `tokenable_id`, `tokenable_type`, `updated_at`, `created_at`) values ([email protected], 85dbe44c32a999a01f4a97d9c9eab0710125a6ac5f861ab546a5822f61015b23, [\"*\"], ?, App\\Models\\User, 2022-03-20 19:16:43, 2022-03-20 19:16:43))

I think the cause of the error is that I am using uuid as the primary key in the users table

Schema::create('users', function (Blueprint $table) {
      $table->uuid('uuid')->primary();
      ...
});

UPDATE

My User Model

...
class User extends Authenticatable
{
    use HasUUID;
    use HasApiTokens;
    use HasFactory;
    use Notifiable;
    use HasRoles;

    ...

    public function tokens()
    {
        return $this->morphMany(Sanctum::$personalAccessTokenModel, 'tokenable', "tokenable_type", "tokenable_id");
    }
    
    ...
}

Any help would be appreciated.


Solution

  • Sorry for late reply. I answer with the solution for anyone who is having the same problem as above. The problem is in my UUId Traits. We should use boot magic method as Laravel suggested when we want to create our own Traits.

    Solution:

    Using App\Traits\HasUUID with the correct code

    <?php
    
    namespace App\Traits;
    
    use Illuminate\Database\Eloquent\Model;
    use Illuminate\Support\Str;
    
    trait HasUUID
    {
        /**
         * Boot functions from Laravel.
         */
        // protected static function boot() <- This line is INCORRECT
        protected static function bootHasUUID()
        {
            static::creating(function (Model $model) {
                $model->primaryKey = 'uuid';
                $model->keyType = 'string'; // In Laravel 6.0+ make sure to also set $keyType
                $model->incrementing = false;
    
                if (empty($model->{$model->getKeyName()})) {
                    $model->{$model->getKeyName()} = Str::uuid()->toString();
                }
            });
        }
    
        /**
         * Get the value indicating whether the IDs are incrementing.
         *
         * @return bool
         */
        public function getIncrementing()
        {
            return false;
        }
    
        /**
         * Get the auto-incrementing key type.
         *
         * @return string
         */
        public function getKeyType()
        {
            return 'string';
        }
    }
    
    

    And finally, add the App\Traits\HasUUID in User Model.

    ...
    use App\Traits\HasUUID;
    ...
    class User extends Authenticatable
    {
        use HasUUID;
        ...
    }
    

    No need to customize Sanctum's Model. Thank you so much @Hussain, @Dharman