'Autheticate via Laravel Sanctum by passing token as a GET query parameter
I know the dangers of passing the token as a GET parameter. I've seen this and this. However, in my case there is no other way because the route will get called by a script which I have no influence on.
I think I should implement a custom Guard which extends the Illuminate\Auth\RequestGuard
and override the public function user()
method. What I don't understand is, where does $this->callback
point to? dd
says it's an instance of Laravel\Sanctum\Guard
.. but which method?
Laravel\Sanctum\Guard {#265 ▼
#auth: Illuminate\Auth\AuthManager {#267 ▶}
#expiration: null
#provider: null
}
Solution 1:[1]
I have a solution now.. I ended up extending Laravel\Sanctum\Guard
and registering a new Illuminate\Auth\RequestGuard
with the custom Sanctum Guard.
Here is the result:
app/Services/Auth/CustomSanctumGuard.php
<?php
namespace App\Services\Auth;
use Arr;
use Illuminate\Http\Request;
use Laravel\Sanctum\Events\TokenAuthenticated;
use Laravel\Sanctum\Guard;
use Laravel\Sanctum\Sanctum;
use Laravel\Sanctum\TransientToken;
class CustomSanctumGuard extends Guard
{
/**
* Retrieve the authenticated user for the incoming request.
*
* @param \Illuminate\Http\Request $request
* @return mixed
*/
public function __invoke(Request $request)
{
if ($token = $request->bearerToken() ?: $request->token) {
$model = Sanctum::$personalAccessTokenModel;
$accessToken = $model::findToken($token);
if (! $this->isValidAccessToken($accessToken) ||
! $this->supportsTokens($accessToken->tokenable)) {
return;
}
$tokenable = $accessToken->tokenable->withAccessToken(
$accessToken
);
event(new TokenAuthenticated($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();
}
return $tokenable;
}
}
}
app/Providers/AuthServiceProvider.php
<?php
namespace App\Providers;
use Auth;
use Illuminate\Auth\RequestGuard;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Gate;
use App\Services\Auth\CustomSanctumGuard;
class AuthServiceProvider extends ServiceProvider
{
/**
* The policy mappings for the application.
*
* @var array
*/
protected $policies = [
// 'App\Models\Model' => 'App\Policies\ModelPolicy',
];
/**
* Register any authentication / authorization services.
*
* @return void
*/
public function boot()
{
$this->registerPolicies();
Auth::resolved(function ($auth) {
$auth->extend('custom', function ($app, $name, array $config) use ($auth) {
return new RequestGuard(
new CustomSanctumGuard($auth, config('sanctum.expiration'), $config['provider']),
request(),
$auth->createUserProvider($config['provider'] ?? null)
);
});
});
}
}
config/auth.php
<?php
return [
// ...
'guards' => [
'custom' => [
'driver' => 'custom',
'provider' => 'users',
],
// ...
],
// ...
];
config/sanctum.php
<?php
return [
// ...
'guard' => ['custom'],
// ...
];
Solution 2:[2]
I had to let some requests with TOKEN URL to return a pdf content. So I created middleware to validate if a token exists and then add it in to the header response, in that way I took advantage of the "normal" sanctum token validation.
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\Log;
/**
* This middleware check if the request has _token key and adds this into the Authorization header to take advantage of
* the sanctum middleware
*/
class CheckTokenAndAddToHeaderMiddleware
{
/**
* Handle an incoming request.
*
* @param Request $request
* @param Closure(\Illuminate\Http\Request): (\Illuminate\Http\Response|\Illuminate\Http\RedirectResponse) $next
* @return Response|RedirectResponse
*/
public function handle(Request $request, Closure $next)
{
$all = $request->all();
if (isset($all['_token'])) {
Log::debug('token from http param', [$all['_token']]);
$request->headers->set('Authorization', sprintf('%s %s', 'Bearer', $all['_token']));
}
return $next($request);
}
}
Given my requirement, I decided to put this middleware over all the URLs, so I added it before all API calls (it could be different for you).
Kernel.php
/**
* The application's route middleware groups.
*
* @var array
*/
protected $middlewareGroups = [
'api' => [
CheckTokenAndAddToHeaderMiddleware::class,
\Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
'throttle:api',
\Illuminate\Routing\Middleware\SubstituteBindings::class
],
];
I hope this could be useful for someone. Regards.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | Gordon Freeman |
Solution 2 | hizmarck |