在 Laravel 7.0 客製化 Sanctum 認證

前言

一般只有使用者會需要認證,但有時候其他模型也需要認證,例如客戶端、組織或企業等。所幸 Sanctum 使用的關聯是一對多的多態關聯,使得其他的模型可以共用 personal_access_tokens 資料表,為模型建立令牌(token)。

認證模型

如果有一個自訂的模型需要建立令牌,例如 Company,可以將它修改如下:

1
2
3
4
5
6
7
8
9
10
11
12
namespace App\Models;

use Illuminate\Auth\Authenticatable;
use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract;
use Illuminate\Database\Eloquent\Model;
use Laravel\Sanctum\HasApiTokens;

class Company extends Model implements AuthenticatableContract
{
use Authenticatable;
use HasApiTokens;
}

中介層

如果令牌不是由 User 模型所建立,例如由 Company 模型所建立,使用此另牌去存取 User 模型的資料,有可能在 Policy 的隱式綁定中出現問題。

1
2
3
4
public function viewAny(User $user)
{
// 出現錯誤,因為 $user 並不是 User 模型
}

因此需要建立一個 VerifyToken 中介層,來辨別目前的認證是什麼模型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
namespace App\Http\Middleware;

use Closure;
use Illuminate\Auth\AuthenticationException;
use Illuminate\Support\Str;

class VerifyToken
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @param string $model
* @return mixed
* @throws AuthenticationException
*/
public function handle($request, Closure $next, string $model)
{
// 確保當前的認證模型是指定的模型
if (! (class_basename($request->user()) === Str::ucfirst($model))) {
throw new AuthenticationException();
}

return $next($request);
}
}

app/Http/Kernel.php 檔中註冊。

1
2
3
4
protected $routeMiddleware = [
// ...
'token' => \App\Http\Middleware\VerifyToken::class,
];

在路由中使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
Route::prefix('api/user')->middleware([
'auth:sanctum',
'token:user',
])->group(function () {
// 只有使用 User 模型所建立的令牌能夠進入
});

Route::prefix('api/company')->middleware([
'auth:sanctum',
'token:company',
])->group(function () {
// 只有使用 Company 模型所建立的令牌能夠進入
});

令牌模型

如果要客製 Sanctum 的 PersonalAccessToken 模型,可以建立一個自己的 Token 模型來繼承它。

1
2
3
4
5
6
7
8
9
10
11
12
13
namespace App\Models;

use Laravel\Sanctum\PersonalAccessToken;

class Token extends PersonalAccessToken
{
/**
* The table associated with the model.
*
* @var string
*/
protected $table = 'personal_access_tokens';
}

修改 AppServiceProvider 服務提供者,在 boot() 方法中設定 Sanctum 要使用的 Token 模型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
namespace App\Providers;

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

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

/**
* Bootstrap any application services.
*
* @return void
*/
public function boot()
{
Sanctum::usePersonalAccessTokenModel(Token::class);
}
}

遷移檔

如果要客製 Sanctum 的 personal_access_tokens 資料表,可以使用以下指令匯出遷移檔。

1
php artisan vendor:publish --tag=sanctum-migrations

修改 AppServiceProvider 服務提供者,停止使用預設的遷移檔:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
namespace App\Providers;

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()
{
//
}
}