Home > Software engineering >  How to limit device login for SPA web app using Laravel Sanctum
How to limit device login for SPA web app using Laravel Sanctum

Time:02-27

I'm building REST API authentication using Laravel sanctum, I wanna make the user can login in multiple device and it is limited by 2 devices, let's say User A and User B are logged in when the user C log in, the user A is logged out and so on. How to achieve this and what is the concept?

Usually I make a login api when the email and password are correct then return the token.

I've learned this from netflix which is it has limited device to watch movie.

CodePudding user response:

You can simply check how many tokens you've issued to this user from within your personal_access_tokens table, as shown below:

personal_access_tokens table

So just run such query when you are signing in the user just before issueing a new token for them:

$issuedTokens = PersonalAccessToken::where('tokenable_type', User::class)
                                   ->where('tokenable_id', $userId)
                                   ->get();
if ($issuedTokens->count() > 1) {
    $returnMessage = 'You have to remove on of the following devices:';
    $deviceNames = $issuedTokens->pluck('name')->toArray();
}
// Things are fine, proceed

And if you would like to enhance things even further you might want to extend the PersonalAccessToken model by adding the mobile details of the person who is entering and perhaps their country/city of access.

To extend it, add migration & model files like the following:

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

// Remember to change this line, if you wish, back to the old way.
return new class extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('personal_access_tokens', function (Blueprint $table) {
            $table->id();
            $table->morphs('tokenable');
            $table->string('name');
            $table->string('token', 64)->unique();
            $table->string('country_name')->nullable();
            $table->text('abilities')->nullable();
            $table->json('mobile_app_details')->nullable();
            $table->timestamp('last_used_at')->nullable();
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('personal_access_tokens');
    }
};

And your model:

<?php


namespace App\Models;

use Eloquent;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Carbon;
use Laravel\Sanctum\PersonalAccessToken as PersonalAccessTokenAlias;

/**
 * App\Models\PersonalAccessToken
 *
 * @property int $id
 * @property string $tokenable_type
 * @property int $tokenable_id
 * @property string $name
 * @property string $token
 * @property array|null $abilities
 * @property object|null $mobile_app_details
 * @property string|null $country_name
 * @property Carbon|null $last_used_at
 * @property Carbon|null $created_at
 * @property Carbon|null $updated_at
 * @property-read Model|\Eloquent $tokenable
 * @method static Builder|PersonalAccessToken newModelQuery()
 * @method static Builder|PersonalAccessToken newQuery()
 * @method static Builder|PersonalAccessToken query()
 * @method static Builder|PersonalAccessToken whereAbilities($value)
 * @method static Builder|PersonalAccessToken whereCreatedAt($value)
 * @method static Builder|PersonalAccessToken whereId($value)
 * @method static Builder|PersonalAccessToken whereLastUsedAt($value)
 * @method static Builder|PersonalAccessToken whereMobileAppDetails($value)
 * @method static Builder|PersonalAccessToken whereName($value)
 * @method static Builder|PersonalAccessToken whereToken($value)
 * @method static Builder|PersonalAccessToken whereTokenableId($value)
 * @method static Builder|PersonalAccessToken whereTokenableType($value)
 * @method static Builder|PersonalAccessToken whereUpdatedAt($value)
 * @mixin Eloquent
 * @noinspection PhpFullyQualifiedNameUsageInspection
 * @noinspection PhpUnnecessaryFullyQualifiedNameInspection
 */
class PersonalAccessToken extends PersonalAccessTokenAlias
{
    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = [
        'name',
        'token',
        'abilities',
        'mobile_app_details',
        'country_name',
    ];

    protected $casts = [
        'abilities' => 'json',
        'last_used_at' => 'datetime',
        'mobile_app_details' => 'object'
    ];

}

One last important step is to tell Laravel to ignore original migrations and to load the custom model, so in your AppServiceProvider:

<?php

namespace App\Providers;

use App\Models\PersonalAccessToken;
use Illuminate\Support\ServiceProvider;
use Laravel\Sanctum\Sanctum;


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

    /**
     * Bootstrap any application services.
     *
     * @return void
     */
    public function boot()
    {
        Sanctum::usePersonalAccessTokenModel(PersonalAccessToken::class);
        // other lines go here
    }
}

  • Related