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 enablefacade()
andeloquent()
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 theboot()
method. The problem is\Laravel\Passport\Passport::routes();
will not work. Lumen’s router does not implement the interfaceIlluminate\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 havedump(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 overrideforAccessTokens
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
sameh
September 21, 2020 at 6:15 am
What if I need to customize the login criteria?
sameh
September 21, 2020 at 6:17 am
Thank you
What if I need to customize login criteria? for example user is active or email is verified?
Thank you again
Nick Alcala
September 21, 2020 at 8:38 am
In your user model your can add this method.
sameh
December 1, 2020 at 2:50 pm
Thank you
sameh
December 1, 2020 at 2:50 pm
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
Nick Alcala
December 2, 2020 at 9:16 pm
https://github.com/dusterio/lumen-passport/issues/148
I have not personally checked this in depth but I think it is pretty new bug. You can try adding this to your composer.json. 🙂
"lcobucci/jwt": "^3.4"
Umair
February 10, 2021 at 7:45 am
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
Nick Alcala
February 11, 2021 at 10:43 pm
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.