Api Authentication With Laravel Passport

0
2871
Laravel Passport Rest API

In this tutorial, I’ll cover API authentication with laravel passport. I’m using laravel 7.x throughout this tutorial. Laravel passport introduced in laravel 5.3. Make sure you installed 5.3 or later version of laravel framework.

# Requirements

  • Laravel installed in your system.
  • Postman app to test APIs.

# Installing Laravel Passport

Laravel passport provides full OAuth2 implementation and it uses Bearer token in the Authorization header in the request. After installing laravel framework let’s get laravel passport package using:

composer require laravel/passport

Note: If you are using laravel < 5.5 then you should add passport service provider in providers array in app.php. To do so, add the following line into providers array.

/* config > app.php */

'providers' => [
    ....
    Laravel\Passport\PassportServiceProvider::class,    
    ....
]

Next, add HasApiTokensuser trait into the User model. HasApiTokensuser provides some functions to handle the Bearer token and the user’s authentication scope. Replace below code in User model.

/* app > User.php */

<?php

namespace App;

use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Passport\HasApiTokens;

class User extends Authenticatable
{
    use HasApiTokens, Notifiable;

    protected $fillable = [
        'name', 'email', 'password',
    ];

    protected $hidden = [
        'password'
    ];
}

Add Passport::routes function into the AuthServiceProvider to register the routes to distribute access tokens, client and personal tokens. To do that, open AuthServiceProvider and replace code below.

/* app > Providers > AuthServiceProvider.php */

<?php

namespace App\Providers;

use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
use Laravel\Passport\Passport;

class AuthServiceProvider extends ServiceProvider
{
    protected $policies = [
        'App\Model' => 'App\Policies\ModelPolicy',
    ];

    public function boot()
    {
        $this->registerPolicies();
        Passport::routes();
    }
}

Finaly, change the API driver from token to passport in auth.php.

/* config > auth.php */

....
'guards' => [        	
	
	...

    'api' => [
        'driver' => 'passport',
        'provider' => 'users',
        'hash' => false,
    ],
],
....

# Configuring Database

Configuration of laravel passport is completed now. Let’s start with implementing real-life API example. We will create a login, register, home, and logout API. Requesting a login or register API will return Bearer token (access token) in return. You can not access home or logout API without a token.

Laravel passport package comes with inbuilt tables to manage OAuth2 functionalities. Laravel shipped with users table migration, modify it as below. After migrating the database, generate encryption keys for personal access client and password grant client by using the following command.

/* database > migrations > *_create_users_table.php

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateUsersTable extends Migration
{
    public function up()
    {
        Schema::create('users', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->string('email')->unique();
            $table->string('password');
            $table->timestamps();
        });
    }

    public function down()
    {
        Schema::dropIfExists('users');
    }
}
php artisan migrate

php artisan passport:install
Passport Install Keys

Start php development server with following command.

php artisan serve

# API Implementation

First of all, create necessary controllers with the following commands. Keep in mind that the following command will create LoginController and RegisterController in API > Auth folder. HomeController will be created in the API folder because this controller has no connections with authentication. Creating these folders will keep all code structural and manageable.

php artisan make:controller API\Auth\LoginController

php artisan make:controller API\Auth\RegisterController

php artisan make:controller API\HomeController

Next, create some routes in api.php. All of the routes use API namespace. LoginController and RegisterController use Auth namespace too. Check home and logout routes that use auth:api middleware to prevent unauthenticated users to access these routes.

/* routes > api.php */

<?php

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;

Route::group(['namespace' => 'API'], function() {

    Route::group(['namespace' => 'Auth'], function(){
        Route::post('login', 'LoginController@login');
        Route::post('register', 'RegisterController@register');
    });


    Route::group(['middleware' => 'auth:api'], function(){
        Route::post('home', 'HomeController@index');
        Route::post('logout', 'Auth\LoginController@logout');
    });

});

Using a form request method for validating an upcoming request will separate validation logic from business logic. That’s why I’m using form requests for all validations, instead of creating validations in controllers. Remember. Open API > Auth > RegisterController and create register function.

/* app > Http > Controllers > API > Auth > RegisterController.php */

<?php

namespace App\Http\Controllers\API\Auth;

use App\Http\Controllers\Controller;
use App\Http\Requests\RegisterRequest;
use Illuminate\Http\Request;
use App\User;

class RegisterController extends Controller
{
    public function register(RegisterRequest $request)
    {
        $user = User::create($request->all());
        $data['success'] = 1;
        $data['user'] = $user;
        $data['token'] =  $user->createToken('App')->accessToken;

        return response()->json($data, 200);
    }
}

As you can see we created a user with all request inputs. But we need to encrypt a password with the bcrypt method too. Let’s create a mutator in the User model, which will automatically encrypt the password when registering a user. Of course, you can do it on a controller too, but it’s not recommended.

/* app > User.php */

....
public function setPasswordAttribute($value)
{
    $this->attributes['password'] = bcrypt($value);
}
....

Create a RegisterRequest with the following command and replace the whole code in it. Remember, if you are using a form request method to handle validation in API then you need to add failedValidation function as well. Laravel form request method returns errors as an array, so we are using a failedValidation method to modify the result. If you are using form requests without API, then you don’t need to define failedValidation method.

php artisan make:request RegisterRequest
/* app > Http > Requests > RegisterRequest.php */

<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Http\JsonResponse;
use Illuminate\Validation\ValidationException;
use Illuminate\Contracts\Validation\Validator;
use Illuminate\Http\Exceptions\HttpResponseException;


class RegisterRequest extends FormRequest
{

    public function authorize()
    {
        return true;
    }

    public function rules()
    {
        return [
            'name' => 'required|max:50',
            'email' => 'required|email|unique:users|max:100',
            'password' => 'required|min:6',
            'confirm_password' => 'required|same:password',
        ];
    }

    public function messages()
    {
        return [
            'name.required' => 'Please enter name',
            'email.required' => 'Please enter email address',
            'email.email' => 'Please enter valid email address',
            'email.unique' => 'Email address has already been taken',
            'password.required' => 'Please enter password',
            'password.min' => 'Password should be minimum :min characters long',
            'confirm_password.required' => 'Please confirm password',
            'confirm_password.same' => 'Password doesn\'t match',
        ];
    }

    protected function failedValidation(Validator $validator)
    {
        $errors = (new ValidationException($validator))->errors();
        throw new HttpResponseException(response()->json( ['success' => 0,'errors' => $errors], JsonResponse::HTTP_UNPROCESSABLE_ENTITY));
    }
}

Similary, open LoginController and create login method.

/* app > Http > Controllers > API > Auth > LoginController.php */

<?php

namespace App\Http\Controllers\API\Auth;

use App\Http\Controllers\Controller;
use App\Http\Requests\LoginRequest;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;

class LoginController extends Controller
{
    public function login(LoginRequest $request)
    {
        if(Auth::attempt(['email' => $request->email, 'password' => $request->password])){
            $user = Auth::user();
            $data['success'] = 1;
            $data['user'] = $user;
            $data['token'] =  $user->createToken('App')->accessToken;
            return response()->json($data, '200');
        }
        else{
            return response()->json(['success' => '0', 'errors'=>'Invalid Credentials.'], 401);
        }
    }
}

Next, Using following command create LoginRequest and replace code.

/* app > Http > Requests > LoginRequest.php */

<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Http\JsonResponse;
use Illuminate\Validation\ValidationException;
use Illuminate\Contracts\Validation\Validator;
use Illuminate\Http\Exceptions\HttpResponseException;

class LoginRequest extends FormRequest
{
    public function authorize()
    {
        return true;
    }

    public function rules()
    {
        return [
            'email' => 'required|email|max:100',
            'password' => 'required|min:6'
        ];
    }

    public function messages()
    {
        return [
            'email.required' => 'Please enter email address',
            'email.email' => 'Please enter valid email address',
            'password.required' => 'Please enter password',
            'password.min' => 'Password should be minimum :min characters long'
        ];
    }

    protected function failedValidation(Validator $validator)
    {
        $errors = (new ValidationException($validator))->errors();
        throw new HttpResponseException(response()->json( ['success' => 0,'errors' => $errors], JsonResponse::HTTP_UNPROCESSABLE_ENTITY));
    }
}

To create home API, open HomeController and create index method. We will retrive logged in user in this function.

/* app > Http > Controllers > API > HomeController.php */

<?php

namespace App\Http\Controllers\API;

use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;

class HomeController extends Controller
{
    public function index()
    {
        $data['success'] = 1;
        $data['user'] = Auth::user();;
        return response()->json($data, 200);
    }
}

For logout users, we must remove token for a specific user from oauth_access_tokens table. We need a model to access this table. Create OauthAccessToken model with the following command. We don’t need migration because the table is already available.

php artisan make:model OauthAccessToken

Open User model and add hasMany relationship with OauthAccessToken model.

/* app > User.php */

....
public function OauthAcessToken(){
    return $this->hasMany('\App\OauthAccessToken');
}   
....

Next, open LoginController and define logout method.

/* app > Http > Controllers > API > Auth > LoginController.php */

....
public function logout()
{
    if (Auth::check()) {
        Auth::user()->OauthAcessToken()->delete();
        $data['success'] = 1;
        $data['message'] = "Logged out successfully.";
        return response()->json($data, '200');
    }
}
....

# Testing APIs

That’s all API authentication with laravel passport. Let’s test all API with the postman. When using login and register API you will get Bearer token in return. After installing a postman, I created four APIs. All of the APIs using POST method.

  • localhost:8000/api/register
  • localhost:8000/api/login
  • localhost:8000/api/home
  • localhost:8000/api/logout
API list

Let’s start with register API. Use localhost:8000/api/register URL in postman and enter name, email, password, and confirm_password. Sending a request with appropriate attributes will return Bearer token.

Register API
Getting Bearer Token

Similarly, to access login API use localhost:8000/api/login URL and pass email and password into body.

Login API

After getting token you can access let use home API with localhost:8000/api/home URL. Remember, you need to pass two headers, the first one is Accept and its value is application/json and the second one is Authorization and it’s value is Bearer {your-login-or-register-token}.

Home API

At the last, check logout API using localhost:8000/api/logout URL. Same as home API need to pass same headers.

Logout API

Try to access home API again after using logout API. You will notice that you can’t access home API anymore. Try to login and use home API again. Now you will have fully functional Rest API with laravel passport authentication.

# Tips For Postman (Recommended)

As you can see in all APIs, there are common URL localhost:8000/api. When uploading into a demo or live server it will change accordingly. We have four APIs now but what if we have hundreds or more? You need to change it manually every time you change the server. Similarly, after getting Bearer token you need to copy and paste it wherever you need to use authenticated routes like home or logout. It’s a headache right?

I will tell you a solution to these two problems. We will create environment variables for both. We will store common URL in url variable and Bearer token in token variable.

To do that, we need an environment. Click on the upper right button on the postman and click on the Add button.

Environment Screen

After that fill environment name and create two variables url and token. Provide localhost:8000/api URL in url variable value. Keep token variable blank for now.

Environment configuration

Next, select a new working environment from the upper right dropdown to access newly created environment variables. You can create as many environments as you want.

Select New Environment

Now we have access to newly created variables, so open all of the APIs and replace URL to a variable. To access variables use double curly braces like {{url}}. Now test all APIs with hover on {{url}} variable, it will display URL.

Replace url variable

The first problem solved. Now what about token variable, we left blank? We have URL to assign in url variable but the token variable generated only when requesting login or register APIs. Then how can we assign token dynamically into home and logout APIs? First of all assign {{token}} variable in home and logout APIs.

Replace Token Variable

Next, open login and register APIs, which is generating new tokens. Now open the Tests tab in both APIs and put the following code in it. The following code will check the API response and get a token from it. Remember, we left a token variable blank? The following code will assign value in a token variable whenever you access login and register APIs.

var data = JSON.parse(responseBody);
postman.setEnvironmentVariable("token", data.token);
Tests Tab

Now check all APIs again, there is no need to replace URL and change Bearer token anymore.

Thankyou for reading this tutorial. If you like this tutorial, please share.

Learn more about Laravel Framework

LEAVE A REPLY

Please enter your comment!
Please enter your name here