In this post, we are going to dig into REST API building using Laravel 5.4 and Passport OAuth2 server provided by laravel. This is the next part of my post Advance interactive database seeding in Laravel post, we left off at migration and seeding of the database. Let’s build the API which will be consumed by our vue.js front end later.
What is OAuth 2
OAuth 2 is an authorization framework that enables applications to obtain limited access to user accounts on an HTTP service, such as Facebook, GitHub etc. It works by delegating user authentication to the service that hosts the user account and authorizing third-party applications to access the user account. OAuth 2 provides authorization flows for web and desktop applications, and mobile devices.
Laravel Passport
Laravel makes API authentication a breeze using Laravel Passport, which provides a full OAuth2 server implementation for your Laravel application in a matter of minutes.
Setup Laravel Passport
Open up the terminal and pull the passport using composer require laravel/passport
. Now register it in app service providers array under config/app.php
Laravel\Passport\PassportServiceProvider::class.
Next, run these two commands php artisan migrate
and php artisan passport:install
, this will migrate the required database table and set up a Personal access client so you can access the API.
After that let’s add Laravel\Passport\HasApiTokens
traits on our User model.
use Laravel\Passport\HasApiTokens;
use Illuminate\Notifications\Notifiable;
use Illuminate\Foundation\Auth\User as Authenticatable;
class User extends Authenticatable
{
use HasApiTokens, Notifiable;
}
Next Open app/Providers/AuthServiceProvider.php and add Passport::routes
method within the boot method. This method will register the routes necessary to issue access tokens and revoke access tokens, clients, and personal access tokens:
/**
* Register any authentication / authorization services.
*
* @return void
*/
public function boot()
{
$this->registerPolicies();
Passport::routes();
}
One last thing you need to change the API guard driver in config/auth.php
to use the passport.
'guards' => [
...
'api' => [
'driver' => 'passport',
'provider' => 'users',
],
],
That’s all needed to setup passport in your laravel application. Laravel Passport also provides some handy Vue component to create and manage OAuth clients, to use it just publish the vendor resources using php artisan vendor:publish --tag=passport-components
, it will place all component in resources/assets/js/components/passport
folder. You need to register in your app.js to use it.
Vue.component(
'passport-clients',
require('./components/passport/Clients.vue')
);
Vue.component(
'passport-authorized-clients',
require('./components/passport/AuthorizedClients.vue')
);
Vue.component(
'passport-personal-access-tokens',
require('./components/passport/PersonalAccessTokens.vue')
);
After registering run npm run dev
to compile the js and you can use the component anywhere in your app. For now, just add these components in resources/views/home.blade.php
file.
<passport-clients></passport-clients>
<passport-authorized-clients></passport-authorized-clients>
<passport-personal-access-tokens></passport-personal-access-tokens>
Go ahead and register a user to log in, you will need to generate a Personal Access Token to access API. Click Create New Token and give a name and copy the generated token. We will use this to make requests to API using Postman.
API Routes
Now we need to define routes for API resources, open the routes/api.php
and add below routes.
Route::resource(
'videos', 'VideoController',
[ 'except' => ['create', 'edit'] ]
);
Route::resource(
'channels', 'ChannelController',
[ 'except' => ['create', 'edit'] ]
);
Route::resource(
'comments', 'CommentController',
[ 'except' => ['create', 'edit'] ]
);
// Get current user
Route::get('/me', function (Request $request) {
return $request->user();
})->middleware('auth:api');
I am excluding create and edit route from the resource, we don’t need them in our API since they generally used to show the form for creating and edit a particular resource.
Create Resource Controllers
Now we need our resource controllers, let’s generate all of them using
php artisan make:controller VideoController --resource
VideoController
In video controller we will be handling video crud operation with validation and access control, we don’t want some to delete or edit other users video.
use App\Video;
use App\API\ApiHelper;
use App\Repos\Repository;
use Illuminate\Http\Request;
class VideoController extends Controller
{
use ApiHelper;
/**
* @var Repository
*/
protected $model;
public function __construct(Video $video)
{
$this->model = new Repository( $video );
// Protect all except reading
$this->middleware('auth:api', ['except' => ['index', 'show'] ]);
}
public function index()
{
return $this->model->with('user')->latest()->paginate();
}
public function store(Request $request)
{
// run the validation
$this->beforeCreate($request);
// validate the channel id belongs to user
if( ! $request->user()->channels()->find($request->get('channel_id', 0)) ) {
return $this->errorForbidden('You can only add video in your channel.');
}
return $request->user()->videos()
->create(
$request->only($this->model->getModel()->fillable)
);
}
public function show($id)
{
return $this->model->with('user')->findOrFail($id);
}
public function update(Request $request, $id)
{
$this->beforeUpdate($request);
if (! $this->model->update($request->only($this->model->getModel()->fillable), $id) ) {
return $this->errorBadRequest('Unable to update.');
}
return $this->model->find($id);
}
public function destroy($id, Request $request)
{
// run before delete checks
if (! $request->user()->videos()->find($id)) {
return $this->errorNotFound('Video not found.');
}
return $this->model->delete($id) ? $this->noContent() : $this->errorBadRequest();
}
}
Let me explain what’s going on. In construct of controller we are Initialising one field $this->model = new Repository( $video );
which is assigning the Video model using repository, in the second line we are running a middleware on all methods except index
and show
so anyone logged in or not can access the data, but when someone wants to change the data like update, store or delete we need to authenticate user first.
index() method
Now index
method is going to list a paginated list of latest videos, and we are eager loading the user.
store() method
In store method we first run $this->beforeCreate($request)
, it’s just a simple method which is defined in parent Controller runs validation, I kept it in a method so you can always override this to suit your need, but in our API it’s enough. here is the beforeCreate()
implementation detail.
public function beforeCreate($request)
{
// run the validation
$this->validate( $request, $this->model->getModel()->getValidationRules());
}
As you can see its running validation against the request and validation rules are defined on Video model itself.
/**
* Validation rules
*
* @param bool $forUpdate
* @return array
*/
public function getValidationRules($forUpdate = false)
{
$createRule = [
'title' => 'required|max:200',
'description' => 'required|min:10',
'allow_comments' => 'boolean',
'url' => 'required|url',
'thumbnail' => 'required|url',
'channel_id' => 'required|integer'
];
$updateRule = [
'title' => 'max:200',
'description' => 'min:10',
'url' => 'url',
'thumbnail' => 'url'
];
return $forUpdate ? $updateRule : $createRule;
}
After validation, we are checking that user owns the channel in which it’s trying to add a video.
// validate the channel id belongs to user
if( ! $request->user()->channels()->find($request->get('channel_id', 0)) ) {
return $this->errorForbidden('You can only add video in your channel.');
}
Once all is ok we are getting all the fillable fields from $request->only($this->model->getModel()->fillable)
and saving the new video on user channel.
You can find all the Model relations defined on Git repository.
We used ApiHelper trait has which give adds helper methods errorForbidden()
, errorNotFound()
, errorForbidden()
etc to our controller.
show() method
Show is simple, it returns a single video with an eager loaded user.
update() method
Similar to store method, it runs the beforeUpdate()
which triggers the validation with update rules defined on Video model and returns updated video.
destroy() method
Destroying any data is always simple, it just retrieves the video using user has many relation and deletes it.
You probably thinking what’s the use of $this->model = new Repository( $video );
in the next section we are going to cover it.
Repository to Access Data
I have created a base repo which takes an Eloquent Model and gives us some methods to interact, it add a layer on top of database access, so if for any reason you want to change the underlying implementation you can easily do so using this wrapper.
class Repository
{
protected $model;
/**
* Repository constructor.
*
* @param $model
*/
public function __construct( Model $model )
{
$this->model = $model;
}
/**
* Get all records
*
* @param array $columns
* @return mixed
*/
public function all($columns = ['*'])
{
return $this->model->all($columns);
}
/**
* Get Paginated records
*
* @param null $limit
* @param array $columns
* @return mixed
*/
public function paginate($limit = null, $columns = ['*'])
{
return $this->model->paginate($limit, $columns);
}
/**
* Find record by id
*
* @param $id
* @param array $columns
* @return mixed
*/
public function find($id, $columns = ['*'])
{
return $this->model->findOrFail($id, $columns);
}
/**
* Create a new record
*
* @param array $attributes
* @return mixed
*/
public function create(array $attributes)
{
return $this->model->create($attributes);
}
/**
* Update a record
*
* @param array $attributes
* @param $id
* @return mixed
*/
public function update(array $attributes, $id)
{
$record = $this->find($id);
return $record->update($attributes);
}
/**
* Delete a record
*
* @param $id
* @return mixed
*/
public function delete($id)
{
return $this->model->destroy($id);
}
/**
* Eager load a relation
*
* @param mixed $relations
* @return mixed
*/
public function with($relations)
{
return $this->model->with($relations);
}
/**
* Get current model instance
*
* @return mixed
*/
public function getModel()
{
return $this->model;
}
/**
* Set model to work with
*
* @param mixed $model
* @return Repository
*/
public function setModel($model)
{
$this->model = $model;
return $this;
}
}
All methods are commented and easy to understand so I am not explaining it.
Now we have api/videos
endpoint complete. Since we have coded using Repository and all the validation are done on parent Controller using defined rules on Model, now you can just copy paste all the controller code and make few changes to complete the API.
You can test the api in postman using our Personal Access Token generated from dashboard. You will be able to list all the videos, channel and comments, but to create, update and delete you must be authenticated.
That’s all in this article, we have our API working with following endpoints.
| GET|HEAD | api/channels | List paginated channels
| POST | api/channels | Create a channel
| GET|HEAD | api/channels/{id} | Get a single channel by id
| PUT|PATCH | api/channels/{id} | Update a single channel by id
| DELETE | api/channels/{id} | Delete a channel by id
| GET|HEAD | api/comments | List paginated comments
| POST | api/comments | Create a comment
| GET|HEAD | api/comments/{id} | Get a single comment by id
| PUT|PATCH | api/comments/{id} | Update a single comment by id
| DELETE | api/comments/{id} | Delete a comment by id
| GET|HEAD | api/videos | List paginated videos
| POST | api/videos | Create a video
| GET|HEAD | api/videos/{id} | Get a single video by id
| PUT|PATCH | api/videos/{id} | Update a single video by id
| DELETE | api/videos/{id} | Delete a video by id
| GET|HEAD | api/me | Get current authenticated user details
In next post, I will be making the front end where you will be able to signup and create channel and post video, comment on video etc. using Vue.js. See you in next one, please comment if you have any question or suggestions.
When I tried to run gulp. it says gulpfile not found.
Gulp is needs node.js, if you dont have it, install it and run in terminal
npm install
, after this you can run gulpMake sure you have node.js installed, you should pull the dependencies by running
npm install
first. then you can rungulp
I am using laravel 5.4. It doesn’t have any gulpfile by default. I have installed all the dependencies by using npm install. I am getting same error still gulpfile not found.
It seems you don’t have gulp either, follow this and install gulp http://gulpjs.com/ that should fix it
Laravel has switched to npm based build with webpack so you can run
npm run dev
ornpm run watch
to keep watching the changes. I have updates the post accordingly.Thank you so much, I will give it a try again.
thanks man.
I will try to implement in my existing API’s in Laravel 🙂
let me know how it go.
how can we use this for other tables than users table
passport doesn’t allow for this but you can hack it, here is an issue on official repo https://github.com/laravel/passport/issues/161#issuecomment-299690583
how to use passport token to identify the video belongs to the authenticated user
When you make a request to API you need to pass the token also as header
'Authorization' => 'Bearer '.$accessToken,
, then in controller you can always get current authenticated user by callingauth()->user()
or$request->user()
, it will give you a Eloquent model instance, and in this User model we have a relation setup like this.public function videos()
{
return $this->hasMany(Video::class);
}
To get current users videos just call
auth()->user()->videos
. I hope it helpsThank you for the suggestion
In my case i have different model then user called admin and i am not able to use auth to determine ta currently logged in user. What can be the solution for that
How can I test this with Postman as you mentioned using the Personal Access Token (what headers should I supply)? 🙂
Pass a header
Authorization: Bearer your_access_token
Or you can pass as a parameter in url endpoint/?token=access_token
Hi Saquieb,
I was able to successfully setup the API’s, but I have one question, how can I use passport in sub folder of any server. I placed the folder in the public_html and the application was running smoothly but when I tried to use the UI for creating public access token, it showed some error. On investigating I found the URL for oauth token function was omitting the sub directory. can you help me with that ??
Hey Abhishek, Installing Laravel in a subfolder can be pain and it will be security concern since user can access your application files, but its possible, I have been there, most of the Shared Hosting providers don’t offer simple way to edit document root for domain.
There are two option here to solve it:
1. First which I prefer is to create a sub domain this way you can define the document root and point it to
sub-domain/public
2. Use a sub folder to install it with some hack which you can read more about here https://laravel-news.com/subfolder-install
I hope this helps.
Hi Saqueib,
I tried the second mthod. My laravel application is working I am able to login and register new user and do other things, but when I tried to create the the tokens it is showing the same error when I press F12 I get this
warn @ app.js:32582
/oauth/personal-access-tokens Failed to load resource: the server responded with a status of 404 (Not Found)
They are unable to form route
alright, you will need to set the base URL for axios library which is used to make http call in vue components. Open the and add your baseUrl after require
window.axios = require(‘axios’);
axios.defaults.baseURL = ‘https://example.com/your-subfolder’;
Now run the
npm run watch
to compile your changesThis should fix it
Thanks bro.
but my first question on first look is open what ?? Meanwhile I try to search this supposed file on net
It is working beautifully.
Thanks bro .
Glad it worked, yes it’s in bootstrap.js
Hey,
Excellent tutorial. But I am getting one error.
Uncaught ReferenceError: Vue is not defined
Also, I don’t have gulp since Laravel 5.4 comes with webpack and it has following content:
mix.js(‘resources/assets/js/app.js’, ‘public/js’)
.sass(‘resources/assets/sass/app.scss’, ‘public/css’);
Why is it not getting Vue? Am I missing something?
You need to install NPM dependencies by running
npm install
, once its done you can runnpm run watch
to run the build script and watch for any changeI did execute `npm install` followed by `npm run watch` but no errors except that in browser, I still get same error
Uncaught ReferenceError: Vue is not defined
at Object. (app.js:11408)
at __webpack_require__ (app.js:20)
at Object.defineProperty.value (app.js:11375)
at __webpack_require__ (app.js:20)
at app.js:63
at app.js:66
OK, you need to check your app.js for vue.js and also check the packages.json
What laravel version you are on? If it’s 5.5 check php artisan preset command for front-end scaffolding
I am using Laravel Framework 5.4.36
Could you share the app.js and bootstrap.js does it has vue and also in package.json
Thanks Dear !
Where is the models definitions??
http://qcode.in/youtube-like-app-vue-js-laravel/
Thank you!