How to use Client Credentials Grant Tokens for your API authorization with Laravel 5.4’s Passport

In trying to build a very small micro-service I decided to use client credential grant tokens. I didn’t need users or personal tokens. I just needed one of my other sites to be able to access data for this new micro-service.

According to Taylor, “The client credentials grant is suitable for machine-to-machine authentication” which sounds just great. So I set off to build the entire service using TDD with Passport::actingAs() to mimic authorization knowing I’d do a final external test once it all was working.

All is working great and my tests are passing with my testing database. So, I run migrations on my real database, create my passport client with “php artisan passport:client,” give it a user id of 1, “ApiAccess” for the name for funsies, and keep the default for the redirect. This gives me a Client ID and Client secret.

I head over to Postman, pop those in, send a post and get my access token.

So far, so good. Now, I grab the access token, throw it in my headers, and fire off a post to my api waiting to be stunned by my own greatness…

{"error":"Unauthenticated."}

Well shit.

After a 2ish hours, I figured out how to implement it without making a mess. These instructions assume you know what Client Credential Grant Tokens are for (read the above link about “machine-to-machine”) and why you might use them. Also it doesn’t address a funky token expiration issue that some found in postman. This, it turned out, didn’t affect me but did lead me on a wild goose chase for a bit.

To start with, move all your API routes that you want to use client credential authorization on out of:

/routes/api.php

and move them into a new route file titled:

/routes/client_credentials.php

This will allow us to separate these routes so we don’t affect any other necessary API routing in the future.

Next, we need to begin using this new routes file. To do this, I updated:

/app/Providers/RouteServiceProvider.php

by adding a new method and calling it in the existing “map” method. I’ve abbreviated the file to show only the necessary updates:

<?php class RouteServiceProvider extends ServiceProvider { // existing map method public function map() { $this->mapApiRoutes(); $this->mapWebRoutes(); // ref new method for adding client credentials $this->mapClientCredentialRoutes(); } // new client credentials method protected function mapClientCredentialRoutes() { Route::prefix('api') // I still want /api/ urls ->middleware('client_credentials') // new middleware I'll set up in a bit ->namespace($this->namespace) ->group(base_path('routes/client_credentials.php')); // referencing my new routes file } }
RouteServiceProvider.php

Now that our routes are setup, we need to get that middleware working. We’re going to add it into the same file where the rest of our middleware is defined:

/app/Http/Kernel.php

Here I created a new middleware group for client_credentials (“client_credentials” is the string we used in RouteServiceProvider.php for our new middleware). This file has been abbreviated to show only the updated info.

<?php class Kernel extends HttpKernel { protected $middlewareGroups = [ 'web' => [ \App\Http\Middleware\EncryptCookies::class, \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class, \Illuminate\Session\Middleware\StartSession::class, // \Illuminate\Session\Middleware\AuthenticateSession::class, \Illuminate\View\Middleware\ShareErrorsFromSession::class, \App\Http\Middleware\VerifyCsrfToken::class, \Illuminate\Routing\Middleware\SubstituteBindings::class, ], 'api' => [ 'throttle:60,1', 'bindings', ], // my new middleware group 'client_credentials' => [ \Laravel\Passport\Http\Middleware\CheckClientCredentials::class, 'throttle:60,1', 'bindings', ] ]; }
Kernel.php

Here you’ll notice we reference:
\Laravel\Passport\Http\Middleware\CheckClientCredentials::class,

That is the %$%$@ middleware to check the client credentials that is no where to be found in the documentation.

With all that in place, I thought, yeah, this isn’t going to work but I’ll try it anyway. I fire up postman, try to hit an endpoint…

It worked! Yay! Hopefully if you’re banging your head against a keyboard right now trying to figure this out, like I was, this article will help.


I pulled ideas from this posts but the implementation was a bit awkward.

  • Juan Carlos Contreras Ontivero

    Hi filljoyner, this post solved my problem. Good job! Thank you

    • filljoynerful

      Hi Juan,
      Glad you found this helpful! It frustrates me to no end. 🙂
      Phil

  • Kevin Hernan Vargas Londoño

    Hello sr, many thanks for this, i was going to destroy my pc searching almost 3 hours for the $!”*·$!· problem.
    Laravel needs to add this to their documentation.

    • filljoynerful

      Ha! The same here. It was beyond frustrating. I’m glad this helped.

  • Muswanto Tan

    Thank u very much sir.. Your post save alot my time. In /routes/client_credentials.php must using ->middleware(‘client_credentials’); instead of auth:api

    • filljoynerful

      Glad it helped!

  • Howdy… I think I need a similar thing. My plan is to use the client credential grant to authorize the frontend first party ios app. One question… how did you (or _do_ you) generate a clientID and secret that’s not associated with a user from the users table? My understanding is that this token ideal for the machine to machine (or in my case, iOS app frontend to laravel api backend) authentication. Seems like this shouldn’t involve a user. Is there an artisan command to generate this one?

    • well, okay then… `php artisan passport:client –password` will create the oauth_client entry. This is a “password grant client” entry. Is that what I want for the “Client Credentials Grant”?

      In any case after punching in ‘grant_type:client_credentials, client_id:4, client_secret:[artisan output]’ into Postman, I end up with {“error”:”unsupported_grant_type”,”message”:”The authorization grant type is not supported by the authorization server.”,”hint”:”Check the `grant_type` parameter”}

      Is there another bit of passport installation not covered in passport:install that is needed to use this grant type?

      • Aaaaand lastly, I’ve got it working! Thanks for giving me a place to work this out in a comment thread haha 🙂

        My issue was putting those key:value pairs in the params section of Postman. They need to instead be entered in the Body/form-data key:value area.

        • filljoynerful

          Hey Heidilux, man I’m sorry. I didn’t get notifications on your comments but I’m so glad you got it worked out. I left out the creation part but yeah that’s all console stuff.

          It’s a complicated thing because, with all the pieces (console, code, postman/curl), at any point the error could be with something as simple as postman and you spend hours trying to figure out what’s the matter with the code! I feel you.

          Glad you got it sorted out and sorry I missed your thread!

        • Dushyant Joshi

          you are a life saver

  • Did you get your TDD to work after this change? I’m in a similar place and have it working in postman but when I changed the middleware all my tests fail.

    • filljoynerful

      I did. It was months ago since I looked at the code but, if I remember correctly, the challenge for me was that all my tests were passing properly and it only showed it failed when I did a manual test through postman.

      If the middleware is preventing a test from running because you can’t create these tokens on your test DB (I ran into this too at some point) the you could break that test out into its own file and then bypass the middleware. This link is to old docs but it should still work:

      https://laravel.com/docs/5.2/testing#disabling-middleware

      • I think that was part of the Browser Test Kit that no longer is part of Laravel, because that was the first place I tried to go (although, too, I did want other middleware to exist). What I ended up doing is making a new middleware that does nothing, and then using IOC injection in the test to replace the CheckClientCredentials middleware with the empty one.

        • filljoynerful

          Gotcha. That sounds like a good solution. Thanks for the heads up! I’m starting on my first 5.5 project now so this should be interesting!