Laravel Passport Get Client ID By Access Token

22,849

Solution 1

So, no answers ...

I was able to resolve the issue by consuming my own API, finally I came up with simpler authentication flow, the client need to send their id & secret with each request, then I consumed my own /oauth/token route with the sent credentials, inspired by Esben Petersen blog post.

Once the access token is generated, I append it to the headers of Symfony\Request instance which is under processing.

My final output like this:

<?php

namespace App\Http\Middleware;

use Request;

use Closure;

class AddAccessTokenHeader
{
    /**
     * Octipus\ApiConsumer
     * @var ApiConsumer
     */
    private $apiConsumer;


    function __construct() {
        $this->apiConsumer  = app()->make('apiconsumer');
    }

    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
    public function handle($request, Closure $next)
    {
        $response = $this->apiConsumer->post('/oauth/token', $request->input(), [
            'content-type' => 'application/json'
        ]);


        if (!$response->isSuccessful()) {
            return response($response->getContent(), 401)
                    ->header('content-type', 'application/json');
        }

        $response = json_decode($response->getContent(), true);

        $request->headers->add([
            'Authorization'     => 'Bearer ' . $response['access_token'],
            'X-Requested-With'  => 'XMLHttpRequest'
        ]);

        return $next($request);

    }
}

I used the above middleware in conjunction with Passport's CheckClientCredentials.

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

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

This way, I was able to insure that $request->input('client_id') is reliable and can't be faked.

Solution 2

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

Solution 3

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.

Solution 4

There is a tricky method. You can modify the method of handle in the middleware CheckClientCredentials, just add this line.

        $request["oauth_client_id"] = $psr->getAttribute('oauth_client_id');

Then you can get client_id in controller's function:

public function info(\Illuminate\Http\Request $request)
{
    var_dump($request->oauth_client_id);
}

Solution 5

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

Share:
22,849
Walid Ammar
Author by

Walid Ammar

Full stack developer, [email protected]

Updated on July 05, 2022

Comments

  • Walid Ammar
    Walid Ammar almost 2 years

    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;.