Laravel Passport Get Client ID By Access Token

Viewed 24577

I'm writing a tiny sms gateway to be consumed by a couple of projects,

I implemented laravel passport authentication (client credentials grant token)

Then I've added CheckClientCredentials to api middleware group:

protected $middlewareGroups = [
    'web' => [
       ...
    ],

    'api' => [
        'throttle:60,1',
        'bindings',
        \Laravel\Passport\Http\Middleware\CheckClientCredentials::class
    ],
];

The logic is working fine, now in my controller I need to get client associated with a valid token.

routes.php

Route::post('/sms', function(Request $request) {
    // save the sms along with the client id and send it

    $client_id = ''; // get the client id somehow

    sendSms($request->text, $request->to, $client_id);
});

For obvious security reasons I can never send the client id with the consumer request e.g. $client_id = $request->client_id;.

10 Answers

I use this, to access the authenticated client app...

$bearerToken = $request->bearerToken();
$tokenId = (new \Lcobucci\JWT\Parser())->parse($bearerToken)->getHeader('jti');
$client = \Laravel\Passport\Token::find($tokenId)->client;

$client_id = $client->id;
$client_secret = $client->secret;

Source

However the answer is quite late, i got some errors extracting the JTI header in Laravel 6.x because the JTI is no longer in the header, but only in the payload/claim. (Using client grants)

local.ERROR: Requested header is not configured {"exception":"[object] (OutOfBoundsException(code: 0): Requested header is not configured at /..somewhere/vendor/lcobucci/jwt/src/Token.php:112)

Also, adding it in a middleware was not an option for me. As i needed it on several places in my app.

So i extended the original Laravel Passport Client (oauth_clients) model. And check the header as well as the payload. Allowing to pass a request, or use the request facade, if no request was passed.

<?php

namespace App\Models;

use Illuminate\Support\Facades\Request as RequestFacade;
use Illuminate\Http\Request;
use Laravel\Passport\Client;
use Laravel\Passport\Token;
use Lcobucci\JWT\Parser;

class OAuthClient extends Client
{
    public static function findByRequest(?Request $request = null) : ?OAuthClient
    {
        $bearerToken = $request !== null ? $request->bearerToken() : RequestFacade::bearerToken();

        $parsedJwt = (new Parser())->parse($bearerToken);

        if ($parsedJwt->hasHeader('jti')) {
            $tokenId = $parsedJwt->getHeader('jti');
        } elseif ($parsedJwt->hasClaim('jti')) {
            $tokenId = $parsedJwt->getClaim('jti');
        } else {
            Log::error('Invalid JWT token, Unable to find JTI header');
            return null;
        }

        $clientId = Token::find($tokenId)->client->id;

        return (new static)->findOrFail($clientId);
    }
}

Now you can use it anywhere inside your laravel app like this:

If you have $request object available, (for example from a controller)

$client = OAuthClient::findByRequest($request);

Or even if the request is not available somehow, you can use it without, like this:

$client = OAuthClient::findByRequest();

Hopefully this useful for anyone, facing this issue today.

The OAuth token and client information are stored as a protected variable in the Laravel\Passport\HasApiTokens trait (which you add to your User model).

So simply add a getter method to your User model to expose the OAuth information:

public function get_oauth_client(){
  return $this->accessToken->client;
}

This will return an Eloquent model for the oauth_clients table

In the latest implementation you can use:

    use Laravel\Passport\Token;
    use Lcobucci\JWT\Configuration;
    
    $bearerToken = request()->bearerToken();
    $tokenId = Configuration::forUnsecuredSigner()->parser()->parse($bearerToken)->claims()->get('jti');
    $client = Token::find($tokenId)->client;

as suggested here: https://github.com/laravel/passport/issues/124#issuecomment-784731969

As I can see the above answer are old and most importantly it dose not work with laravel 8 and php 8, so I have found a way to get the client id of the access token ( current request )

the answer is basically making a middleware, and add it to all routes you want to get the client id.

<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Nyholm\Psr7\Factory\Psr17Factory;
use Laravel\Passport\TokenRepository;
use League\OAuth2\Server\ResourceServer;
use Illuminate\Auth\AuthenticationException;
use League\OAuth2\Server\Exception\OAuthServerException;
use Symfony\Bridge\PsrHttpMessage\Factory\PsrHttpFactory;

class SetPassportClient
{

    /**
     * The Resource Server instance.
     *
     * @var \League\OAuth2\Server\ResourceServer
     */
    protected $server;

    /**
     * Token Repository.
     *
     * @var \Laravel\Passport\TokenRepository
     */
    protected $repository;

    /**
     * Create a new middleware instance.
     *
     * @param  \League\OAuth2\Server\ResourceServer  $server
     * @param  \Laravel\Passport\TokenRepository  $repository
     * @return void
     */
    public function __construct(ResourceServer $server, TokenRepository $repository)
    {
        $this->server = $server;
        $this->repository = $repository;
    }

    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
    public function handle(Request $request, Closure $next)
    {
        $psr = (new PsrHttpFactory(
            new Psr17Factory,
            new Psr17Factory,
            new Psr17Factory,
            new Psr17Factory
        ))->createRequest($request);

        try {
            $psr = $this->server->validateAuthenticatedRequest($psr);
        } catch (OAuthServerException $e) {
            throw new AuthenticationException;
        }
        
        $token = $this->repository->find($psr->getAttribute('oauth_access_token_id'));

        if (!$token)
            abort(401);

        $request->merge(['passportClientId' => $token->client_id]);

        return $next($request);
    }
}

Add the middleware to app\Http\Kernel.php

protected $routeMiddleware = [
    .
    .
    'passport.client.set' => \App\Http\Middleware\SetPassportClient::class
];

Finaly in the routes add the middleware

Route::middleware(['client', 'passport.client.set'])->get('/test-client-id', function (Request $request){
 dd($request->passportClientId); // this the client id
});

Sorry for the long answer, but I want it to be very clear to any all.

All of the code was inspired by laravel CheckCredentials.php

public function handle($request, Closure $next, $scope)
{
    if (!empty($scope)) {
        $psr      = (new DiactorosFactory)->createRequest($request);
        $psr      = $this->server->validateAuthenticatedRequest($psr);
        $clientId = $psr->getAttribute('oauth_client_id');
        $request['oauth_client_id'] = intval($clientId);
       }

    return $next($request);
}

put above to your middleware file, then you can access client_id by request()->oauth_client_id

In a method you can easily get by:

$token = $request->user()->token();

$clientId = $token['client_id'];
Related