16 Mar 2019
cat: API, Laravel, PHP
8 Comments

Lumen API Tutorial – Authentication using Laravel Passport

Introduction

In this tutorial we will install Laravel Passport to a Lumen project. Laravel Passport does not work out of the box with Lumen. If you don’t want to do these steps just to make it work then maybe you should consider using a composer package.

Installing Passport

First run these commands to install Laravel Passport and copy the migrations to your Lumen project.

composer require laravel/passport
cp -r vendor/laravel/passport/database/migrations/. database/migrations

If we try to just register \Laravel\Passport\PassportServiceProvider::class to the container in bootstrap/app.php and testing the home route or run artisan, we will get a runtime exception “A facade root has not been set.”.

$app->register(\Laravel\Passport\PassportServiceProvider::class);

One way we can solve this is to override the PassportServiceProvider and use app() function to get the services or classes that we need. The easiest way is to just enable facade() and eloquent() since Passport uses Eloquent.

Open bootstrap/app.php and uncomment the following lines.

$app->withFacades();
$app->withEloquent();

Now we will get another error “Call to undefined function Laravel\Passport\config_path()”. To solve this we really do need to override a method on PassportServiceProvider. We need to override offerPublishing and replace config_path(‘passport.php’) with app(‘config’)->get(‘passport.php’).

I put my new classes in app/providers/Passport directory since we will be overriding 2 more classes later.

<?php

namespace App\Providers\Passport;

use Laravel\Passport\PassportServiceProvider;

class LumenPassportServiceProvider extends PassportServiceProvider
{

    /**
     * Setup the resource publishing groups for Passport.
     *
     * @return void
     */
    protected function offerPublishing()
    {
        if ($this->app->runningInConsole()) {
            $this->publishes([
                __DIR__ . '/../config/passport.php' => app('config')->get('passport.php'),
            ], 'passport-config');
        }
    }

}

Then we update bootstrap/app.php to use our new service provider instead of the one in Passport.

$app->register(\App\Providers\Passport\LumenPassportServiceProvider::class);

If we run artisan again we should not get an error anymore and we can run artisan passport:install.

We will now register some middleware and then register the routes for OAuth that Passport provided. In bootstrap/app.php add these codes in the middlewares section.

$app->routeMiddleware([
    'auth' => App\Http\Middleware\Authenticate::class,
    'client' => \Laravel\Passport\Http\Middleware\CheckClientCredentials::class,
]);

To register the OAuth routes from Passport. We can uncomment the $app->register(App\Providers\AuthServiceProvider::class); in bootstrap/app.php add register the routes in the boot() method. The problem is \Laravel\Passport\Passport::routes(); will not work. Lumen’s router does not implement the interface Illuminate\Contracts\Routing\Registrar.

First we need to override \Laravel\Passport\Passport::routes(); method and \Laravel\Passport\RouteRegistrar‘s constructor to have it take Lumen’s built-in router.

<?php

namespace App\Providers\Passport;

use Laravel\Lumen\Routing\Router;
use Laravel\Passport\RouteRegistrar;

class LumenRouteRegistrar extends RouteRegistrar
{
    /**
     * Create a new route registrar instance.
     *
     * @param  Router $router
     */
    public function __construct(Router $router)
    {
        $this->router = $router;
    }
}
<?php

namespace App\Providers\Passport;

use Laravel\Passport\Passport;

class LumenPassport extends Passport
{
    /**
     * Binds the Passport routes into the controller.
     *
     * @param  callable|null  $callback
     * @param  array  $options
     * @return void
     */
    public static function routes($callback = null, array $options = [])
    {
        $callback = $callback ?: function ($router) {
            $router->all();
        };

        $defaultOptions = [
            'prefix' => 'oauth',
            'namespace' => '\Laravel\Passport\Http\Controllers',
        ];

        $options = array_merge($defaultOptions, $options);

        app('router')->group($options, function ($router) use ($callback) {
            $callback(new LumenRouteRegistrar($router));
        });
    }
}

In our AuthServiceProvider we will use \App\Providers\Passport\LumenPassport::routes();.
To know if the routes are now registered we can have dump(app('router')->getRoutes()); on a test route and view it on postman or a browser.

We still cannot get a token because we will get a “ReflectionException: Class throttle does not exist” error.
Update LumenRouteRegistrar and override forAccessTokens method to remove the use of "throttle" middleware.

<?php

namespace App\Providers\Passport;

use Laravel\Lumen\Routing\Router;
use Laravel\Passport\RouteRegistrar;

class LumenRouteRegistrar extends RouteRegistrar
{
    /**
     * Create a new route registrar instance.
     *
     * @param  Router $router
     */
    public function __construct(Router $router)
    {
        $this->router = $router;
    }

    /**
     * Register the routes for retrieving and issuing access tokens.
     *
     * @return void
     */
    public function forAccessTokens()
    {
        $this->router->post('/token', [
            'uses' => 'AccessTokenController@issueToken',
            'as' => 'passport.token',
        ]);

        $this->router->group(['middleware' => ['web', 'auth']], function ($router) {
            $router->get('/tokens', [
                'uses' => 'AuthorizedAccessTokenController@forUser',
                'as' => 'passport.tokens.index',
            ]);

            $router->delete('/tokens/{token_id}', [
                'uses' => 'AuthorizedAccessTokenController@destroy',
                'as' => 'passport.tokens.destroy',
            ]);
        });
    }

}

After that we will still get some reflection errors that we can fix by going back to our service provider’s boot method to register the needed services.

<?php

namespace App\Providers\Passport;

use Laravel\Passport\PassportServiceProvider;

class LumenPassportServiceProvider extends PassportServiceProvider
{

    /**
     * Bootstrap the application services.
     */
    public function boot()
    {
        $this->app->singleton(\Illuminate\Database\Connection::class, function () {
            return $this->app['db.connection'];
        });

        $this->app->singleton(\Illuminate\Hashing\HashManager::class, function ($app) {
            return new \Illuminate\Hashing\HashManager($app);
        });

        if ($this->app->runningInConsole()) {

            $this->registerMigrations();

            $this->commands([
                \Laravel\Passport\Console\InstallCommand::class,
                \Laravel\Passport\Console\ClientCommand::class,
                \Laravel\Passport\Console\KeysCommand::class,
            ]);
        }
    }

    /**
     * Setup the resource publishing groups for Passport.
     *
     * @return void
     */
    protected function offerPublishing()
    {
        if ($this->app->runningInConsole()) {
            $this->publishes([
                __DIR__ . '/../config/passport.php' => app('config')->get('passport.php'),
            ], 'passport-config');
        }
    }

}

This should fix the reflection errors while still making the passport artisan commands available. If we try getting a token using postman we should finally get a token.

To protect a route with client credentials grant use the “client” middleware that we registered earlier like in the example below.

$router->group([
    'middleware' => 'client'
], function (\Laravel\Lumen\Routing\Router $router) {
    $router->get('/test-client', function () {
        return 'it worked';
    });
});

Password Grant

Password grant will still not work because there is some configurations we need to do. That is to write these in config/auth.php.

<?php

return [

    'defaults' => [
        'guard' => env('AUTH_GUARD', 'api'),
        'passwords' => 'users',
    ],

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

    'providers' => [
        'users' => [
            'driver' => 'eloquent',
            'model' => \App\User::class,
        ],
    ],

];

Don’t forget to add $app->configure('auth'); in boostrap/app.php.

We can now try getting an access token.

To protect a route with password credetials i.e. requires a logged in user use the “auth:api” middleware like below.

$router->group([
    'middleware' => 'auth:api'
], function (\Laravel\Lumen\Routing\Router $router) {
    $router->get('/test-password', function () {
        return 'it worked';
    });
});

That is it for this tutorial. Thanks for reading.

References


  1. What if I need to customize the login criteria?


  2. Thank you
    What if I need to customize login criteria? for example user is active or email is verified?
    Thank you again


  3. Hi,
    Hi, I am getting “Call to undefined method Lcobucci\JWT\Token\Plain::getClaim() (500 Internal Server Error)” when I try to call the oauth/token route


  4. I am able to generate the access token against client credentials, but when my client will get access token how would I be able to authorize and give data to that client against the access token


    • Sorry for the late reply. For the sample codes it is not quite straight forward. There are various solutions out there that looks kind of like this.

      
      $router->get('/test-client', function (Request $request, \Lcobucci\JWT\Parser $parser, \Laravel\Passport\TokenRepository $repository) {
              $token = $parser->parse($request->bearerToken());
              $accessToken = $repository->find($token->getClaim('jti'));
              dd($accessToken->client);
              return 'it worked ';
          });
      

Post A Comment