Create REST API with authentication using Laravel Passport

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.

Source Code

33 Responses to “Create REST API with authentication using Laravel Passport”

    • Saqueib

      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 calling auth()->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 helps

      • jagdish kunwar

        Thank 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

  1. Abhishek Choudhery

    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.

      • Abhishek Choudhery

        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

  2. Milan Chheda

    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?