Skip to content

Instantly share code, notes, and snippets.

@stevebauman
Last active November 11, 2021 21:44
Show Gist options
  • Save stevebauman/b490f474e801d58ba97df8c45ac3ad4a to your computer and use it in GitHub Desktop.
Save stevebauman/b490f474e801d58ba97df8c45ac3ad4a to your computer and use it in GitHub Desktop.
A Laravel Sanctum "token" User provider
<?php
return [
'defaults' => [
'guard' => 'api',
'passwords' => 'users',
],
'guards' => [
'api' => [
'driver' => 'token',
'provider' => 'users',
'hash' => false,
],
],
'providers' => [
'users' => [
'driver' => 'sanctum:token',
'model' => App\Models\User::class,
],
],
];
<?php
Auth::provider('sanctum:token', function ($app, $config) {
return new SanctumTokenUserProvider($app['hash'], $config['model']);
});
<?php
namespace App\Providers;
use Illuminate\Auth\EloquentUserProvider;
use Illuminate\Support\Arr;
use Laravel\Sanctum\HasApiTokens;
use Laravel\Sanctum\PersonalAccessToken;
use Laravel\Sanctum\Sanctum;
class SanctumTokenUserProvider extends EloquentUserProvider
{
/**
* Retrieve a user by the given credentials.
*
* @param array $credentials
*
* @return null|\Illuminate\Contracts\Auth\Authenticatable
*/
public function retrieveByCredentials(array $credentials)
{
// If we've been given a password, we'll pass the credentials
// to the parent method using the Eloquent user provider
// instead, which will handle this scenario for us.
if (Arr::has($credentials, 'password')) {
return parent::retrieveByCredentials($credentials);
}
if (! $tokenKey = $this->firstCredentialKey($credentials)) {
return;
}
if (! $token = $credentials[$tokenKey]) {
return;
}
$model = Sanctum::$personalAccessTokenModel;
$accessToken = $model::findToken($token);
if (
! $this->isValidAccessToken($accessToken)
|| ! $this->supportsTokens($accessToken->tokenable)
) {
return;
}
return tap($accessToken, function (PersonalAccessToken $token) {
$this->touch($token);
})->tokenable->withAccessToken(
$accessToken
);
}
/**
* Touch the last used at timestamp of the access token.
*
* @param PersonalAccessToken $accessToken
*
* @return void
*/
protected function touch(PersonalAccessToken $accessToken)
{
if (
method_exists($accessToken->getConnection(), 'hasModifiedRecords')
&& method_exists($accessToken->getConnection(), 'setRecordModificationState')
) {
tap($accessToken->getConnection()->hasModifiedRecords(), function ($hasModifiedRecords) use ($accessToken) {
$accessToken->forceFill(['last_used_at' => now()])->save();
$accessToken->getConnection()->setRecordModificationState($hasModifiedRecords);
});
} else {
$accessToken->forceFill(['last_used_at' => now()])->save();
}
}
/**
* Determine if the tokenable model supports API tokens.
*
* @param mixed $tokenable
*
* @return bool
*/
protected function supportsTokens($tokenable = null)
{
return $tokenable && in_array(HasApiTokens::class, class_uses_recursive(
get_class($tokenable)
));
}
/**
* Determine if the provided access token is valid.
*
* @param mixed $accessToken
*
* @return bool
*/
protected function isValidAccessToken($accessToken)
{
if (! $accessToken) {
return false;
}
$expiration = config('sanctum.expiration');
$isValid =
(! $expiration || $accessToken->created_at->gt(now()->subMinutes($expiration)))
&& $this->hasValidProvider($accessToken->tokenable);
if (is_callable(Sanctum::$accessTokenAuthenticationCallback)) {
$isValid = (bool) (Sanctum::$accessTokenAuthenticationCallback)($accessToken, $isValid);
}
return $isValid;
}
/**
* Get the first key from the credential array.
*
* @param array $credentials
*
* @return null|string
*/
protected function firstCredentialKey(array $credentials)
{
foreach ($credentials as $key => $value) {
return $key;
}
}
/**
* Determine if the tokenable model matches the provider's model type.
*
* @param \Illuminate\Database\Eloquent\Model $tokenable
*
* @return bool
*/
protected function hasValidProvider($tokenable)
{
return $tokenable instanceof $this->model;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment