'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