Laravel 5.4 OAuth with Dingo internal requests
Asked Answered
Z

1

10

I am using Laravel 5.4 with Dingo API, and I'm trying to get Laravel's OAuth 2.0 (Passport) to work with Internal Dingo requests. Previously, I was using JWT, but now I wish to use OAuth. This is my previous dispatcher code which passes along the required token to perform the authentication on an internal request.

public function getDispatcher()
{
    $token = JWTAuth::fromUser(Auth::user());
    return $this->api->header('Authorization','Bearer'.$token)->be(Auth::user());
}

Now that I'm using OAuth to authenticate, my JavaScript code manages to get authentication simply by passing a cookie using this method in the JavaScript, which works perfectly.

Now I need to modify the getDispatcher() method to get the OAuth token on an "internal request" within Dingo. Does anyone have any tips on how to do this? In theory I could create a personal access token for every user but this seems like overkill just for an internal request. Any advice or approaches appreciated. How can I get the OAuth token without going through the complete OAuth flow, or alternatively, how can I turn off authentication just for internal requests.

Update based on answer below:

'api.auth' on its own on the route (just Dingo) and the internal request works. auth:api (Passport) + api.auth and I get the method not allowed on internal requests this comes back as JSON. {"message":"405 Method Not Allowed"} now when trying to call an internal POST request. (It looks like a 301 redirect to the login page occurs when trying to POST to these routes, and in turn causes the API path to turn into a GET somehow thus throwing the 405 error).

API requests via Postman work in the inverse capacity. Can't find a user when both active (['middleware' => ['auth:api','api.auth']) when (auth:api just Passport) active it works fine.

Zamindar answered 20/11, 2017 at 0:1 Comment(1)
See if this helps esbenp.github.io/2017/03/19/modern-rest-api-laravel-part-4 ?Crunode
U
7

If I read the question correctly, it looks like we're trying to use two authentication providers—Dingo's and Passport—at the same time. Correct me if I misunderstand, but it doesn't seem like we actually need to use both in this project. For most applications, we can perform authentication using Passport, and simply pass the result to Dingo.

We achieve this by creating a custom authentication provider that bridges Dingo with the authentication performed by Passport:

use Dingo\Api\Contract\Auth\Provider;
use Illuminate\Auth\AuthManager;
...
class PassportDingoAuthProvider implements Provider
{
    protected $guard;

    public function __construct(AuthManager $auth) 
    {
        $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.');
    }
}

As we can see, the Dingo auth provider shown above just hooks into the Laravel auth system to forward the User when authenticated. The 'api' guard specified in the constructor should match the guard configured for Passport (we usually add an 'api' entry to the 'guards' array in config/auth.php):

'guards' => [
    ...
    'api' => [
        'driver' => 'passport',
        'provider' => 'users',
    ],
],

Then, we need to register the custom provider with Dingo in config/api.php:

'auth' => 
    'passport' => App\Providers\PassportDingoAuthProvider::class
]

Now we can declare protected routes that use both the Passport auth middleware (auth:api) and the Dingo auth middleware (api.auth):

$api->get('endpoint', function () { ... })->middleware('auth:api', 'api.auth');

We can create a middleware group in app/Http/Kernel.php that combines these if desired:

protected $middlewareGroups = [
    ...
    'auth:api-combined' => [
        'auth:api', // Passport
        'api.auth'  // Dingo
    ]
];

By the time the application needs to call the internal API, the client should already be authenticated because typical Laravel applications handle authentication in the middleware stack. As you know, we can simply pass on the authenticated User to a Dingo endpoint if needed:

return $this->api->be(auth()->user())->get('endpoint');

...but this shouldn't be necessary with the auth provider shown above. Dingo will resolve the authenticated user from Passport's auth guard.

Here's a sample project that combines these concepts.

Now that I'm using OAuth to Authenticate, my Javascript code manages to get authentication simply by passing a cookie using this method in the Javascript...I need to modify the getDispatcher method to get the OAuth token on an 'Internal request' within Dingo.

When we use the CreateFreshApiToken middleware, Laravel generates an encrypted JWT on-the-fly. We can create one of these tokens manually:

use Firebase\JWT\JWT; // installed with Passport
...
$token = JWT::encode([
    'sub' => auth()->id(),
    'csrf' => session()->token(),
    'expiry' => Carbon::now()->addMinutes(config('session.lifetime')),
], app('encrypter')->getKey());

We can see this isn't a standard OAuth access token—Passport only uses these for web requests. Alternatively, we can fetch this value from the cookie passed back from JavaScript:

$token = request()->cookie(Passport::cookie());

However, we shouldn't need this token if we integrate Dingo with Passport as described above.

Unremitting answered 24/11, 2017 at 22:8 Comment(6)
This looks great.. will let you know how I get on.Zamindar
Tried the solution described.. unfortunately close but no cigar. A sample git project would be awesome to see structure but my hunch is I'm missing something in the middleware atm.Zamindar
@Paul when I have some time today or tomorrow I'll try to throw a sample project together.Unremitting
that would be awesome. Appreciate the time taken on this so far.Zamindar
@Paul to address the update: make sure we're calling $dispatcher->post('internal-endpoint') and not get() to solve the 405 error. Requests using Postman won't work without a real access token because we're not getting Passport's JWT sent by CreateFreshApiToken. Here's a sample project that should give some ideas.Unremitting
I have spent quite a long time trying to work out why internal requests did not work from my web controllers. Was I wrong in thinking this was possible? I have got my internal requests working from API controllers fine...Blackmarket

© 2022 - 2024 — McMap. All rights reserved.