Search code examples
phplaravelauthenticationlaravel-passportdingo-api

Laravel dingo/api - Using internal routes with Passport (L6)


I see quite a few people having a similar issue with this, but no final resolved solutions. I have been trying to get this working for about 24 hours now and still no luck!

Goals

  1. Build and API using Laravel 6 and Dingo API
  2. Be able to consume the API externally, authenticating with Passport oAuth.
  3. Be able to consume the API internally, via ajax, using passports self-authenticating feature.
  4. Be able to consume the API internally, with PHP, using dingo's self-consuming methods.

What I have found out so far

Auth provider order

Most solutions I have seen suggest setting up both the passport auth and dingo alongside one another. This is auth:api (passport) and api.auth (dingo).

// API route middleware
$api->group(['middleware' =>  'auth:api', 'api.auth'], function (Router $api) {
...

The api.auth here is actually a custom auth provider setup in laravel and configured to dingo, which bridges the passport logic into dingo.

// Auth provider
class DingoPassportAuthProvider extends Authorization
{
    protected $guard;

    public function __construct(AuthManager $auth)
    {
        dump('DingoPassportAuthProvider Instantiated');
        $this->guard = $auth->guard('api');
    }

    public function authenticate(Request $request, Route $route)
    {
        if ($this->guard->check()) {
            return $this->guard->user();
        }

        throw new UnauthorizedHttpException('Not authenticated via Passport.');
    }

    public function getAuthorizationMethod()
    {
        return 'Bearer';
    }
}
// Configured in dingo (Api.php)
'auth' => [
    'passport' => \App\Providers\DingoPassportAuthProvider::class,
],

If we put the dingo API provider first in the middleware stack we get:

  • Internal API requests work IF you specify the user for the call with the be() method: $this->api->be($request->user())->get('/api/profile')

  • External API requests and internal AJAX requests authenticate correctly and the user is returned from the custom dingo auth provider, however, for some reason you cannot then access this user from within the API controllers: $user = $request->user(); // null

If we put the Passport API provider first in the middleware stack we get:

  • Internal API requests do not work at all (401 always returned)

  • External API requests and internal AJAX requests work as intended.

  • The authenticate method on the dingo passport provider is no longer called. I think this may have something to do with the 401 returned on internal calls.

I believe the correct way around, is to put the passport authentication first. This way, we authenticate the user before calling the dingo authentication, resulting in 2 things:

  1. Passport works natively as expected.

  2. Dingo internal API calls should now just be able to be called with $this->api->get('/api/profile') (omit defining the user with be()), however this does not work.

At the moment I have the previous configuration. Passport works as intended for external and ajax calls, but the internal dingo calls always return 401.

There are a few boilerplate templates I have checked out and they do not seem to do anything different. I wonder if something changed in L6 to explain why the internal requests do not work.


Solution

  • I have found one work around for now, which gets most of the way there...

    Within the custom dingo auth provider:

    class DingoPassportAuthProvider extends Authorization
    {
        public function authenticate(Request $request, Route $route)
        {
            if (Auth::guard('web')->check()) {
                return Auth::guard('web')->user();
            }
    
            if (Auth::guard('api')->check()) {
                $user = Auth::guard('api')->user();
                Passport::actingAs($user);
    
                return $user;
            }
    
            throw new UnauthorizedHttpException('Not authenticated via Passport.');
        }
    
        public function getAuthorizationMethod()
        {
            return 'Bearer';
        }
    }
    

    This now checks to see if the request is coming from either the web guard (internal request) or the api guard (external or ajax request) and returns the correct user.

    For the api guard, there seems to be an issue that the user is authenticated but not actually available within the controllers. To get around this I added the Passport::actingAs($user). It is probably not best practice, but the guards are now acting as they should and as are all my different scenarios.

    Then in the API route middleware, we only specify the custom dingo provider.

    // API route middleware
    $api->group(['middleware' => 'api.auth'], function (Router $api) {
    ...
    

    One thing to note with this, is dingos be() method does not work quite as expected. Instead you need to switch the user as you would in a general laravel app.

    \Auth::loginUsingId(2);
    $user = $this->api->get('/api/profile');