Click Below to subscribe

How to Implement Passport Authentication in Laravel 2022

Laravel Passport provides a full OAuth2 server implementation for your Laravel application. Passport is built on top of the League OAuth2 server.

In this article, we will implement passport authentication from scratch(laravel 8).

1. Let's create a new laravel project.

 composer create-project laravel/laravel laravel-passport

2. After creating the project install the passport package.

composer require laravel/passport

3. Update your database credentials in .env file.

DB_DATABASE=laravel-passport // your database name
DB_USERNAME=root            // your database username 
DB_PASSWORD=               // your database password

4. Laravel passport package comes up with some migration files for storing OAuth2 clients and access tokens. so you need to hit this command.

php artisan migrate

5. Generate encryption keys.

php artisan passport:install

Note:- The command will create personal access and password grant clients which will be used to generate access tokens.

6. Publish passport config file.

php artisan vendor:publish --tag=passport-config

This will create a passport.php file inside the config folder. this file contains all configurations of the passport.

7. Open passport.php file and update it like this.

    /*
    |--------------------------------------------------------------------------
    | Personal Access Client
    |--------------------------------------------------------------------------
    |
    | If you enable client hashing, you should set the personal access client
    | ID and unhashed secret within your environment file. The values will
    | get used while issuing fresh personal access tokens to your users.
    |
    */

    'personal_access_client' => [
        'id' => env('PASSPORT_PERSONAL_ACCESS_CLIENT_ID'),
        'secret' => env('PASSPORT_PERSONAL_ACCESS_CLIENT_SECRET'),
    ],

    /*
    |--------------------------------------------------------------------------
    | Password Grant Client
    |--------------------------------------------------------------------------
    |
    */

    'password_grant_client' => [
        'id' => env('PASSPORT_PASSWORD_GRANT_CLIENT_ID'),
        'secret' => env('PASSPORT_PASSWORD_GRANT_CLIENT_SECRET'),
    ],

8. Open .env file and update these environment variables which are generated in step 5.

// Replace with your client_id and secret_key
PASSPORT_PERSONAL_ACCESS_CLIENT_ID=1
PASSPORT_PERSONAL_ACCESS_CLIENT_SECRET=xGhkmSlKzthLLmEhvqKGSvCPXcd0ZtujO4MJt58i

PASSPORT_PASSWORD_GRANT_CLIENT_ID=2
PASSPORT_PASSWORD_GRANT_CLIENT_SECRET=u2V6IqBraMA2aUZov3McO6DRPKg0SWDbiZjwcUSX

9. Update your User model.

go to app/Models and open User.php file. after that add Laravel\Passport\HasApiTokens  trait. This trait will provide a few helper methods to your model which allow you to inspect the authenticated user's token and scopes.

Note:- Laravel 8 by default has a Laravel\Sanctum\HasApiTokens trait. so remove that trait.

<?php

namespace App\Models;

use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Passport\HasApiTokens;

class User extends Authenticatable
{
    use HasApiTokens, HasFactory, Notifiable;

10. Update your Auth Guard.

go to config folder and open auth.php. after that update guards array like this.

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

11. Update AuthServiceProvider

go to app/Providers and open AuthServiceProvider.php. after that add Passport::routes() in boot method and uncomment ModelPolicy. also, you can define token expire time like this.

<?php

namespace App\Providers;

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

class AuthServiceProvider extends ServiceProvider
{
    /**
     * The policy mappings for the application.
     *
     * @var array<class-string, class-string>
     */
    protected $policies = [
        'App\Models\Model' => 'App\Policies\ModelPolicy',
    ];

    /**
     * Register any authentication / authorization services.
     *
     * @return void
     */
    public function boot()
    {
        $this->registerPolicies();
        if (!$this->app->routesAreCached()) {
            Passport::routes(); 
        }
        Passport::tokensExpireIn(now()->addDays(15));
        Passport::refreshTokensExpireIn(now()->addDays(30));
        Passport::personalAccessTokensExpireIn(now()->addMonths(6));
    }
}

Note:- Passport::routes() add few oauth routes in our application. all routes prefix with oauth

Passport::routes(null, ['prefix' => 'oauth']);

Example:- http://example.com/oauth/token 

you can also modify this prefix

Passport::routes(null, ['prefix' => 'api/oauth']);

Example:- http://example.com/api/oauth/token

12. Create the AuthController.

php artisan make:controller Api/AuthController

Note:- Here Api is the folder name.

This will create AuthController.php inside app/Http/Controllers/Api directory

13.Update AuthController.php

<?php

namespace App\Http\Controllers\Api;

use App\Http\Controllers\Controller;
use Laravel\Passport\RefreshTokenRepository;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Facades\Http;
use Illuminate\Http\Request;
use App\Models\User;
use Carbon\Carbon;
class AuthController extends Controller
{

    public function register(Request $request)
    {
        $validator = Validator::make($request->all(), [
            'name' => 'required',
            'email' => 'required|email|unique:users',
            'password' => 'required'
        ]);

        if ($validator->fails()) {
            return response()->json($validator->errors(), 400);
        }

        $user = new User();
        $user->name = $request->name;
        $user->email = $request->email;
        $user->password = bcrypt($request->password);
        $user->save();
        return response()->json(['data' => $user]);
    }

    public function login(Request $request)
    {
        $credentials = $request->only(['email', 'password']);
        $validator = Validator::make($credentials, [
            'email' => 'required|email',
            'password' => 'required'
        ]);

        if ($validator->fails()) {
            return response()->json($validator->errors(), 400);
        }

        if (!auth()->attempt($credentials)) {
            return response()->json(['error' => 'Unauthorized'], 401);
        }

        /* ------------ Create a new personal access token for the user. ------------ */
        $tokenData = auth()->user()->createToken('MyApiToken');
        $token = $tokenData->accessToken;
        $expiration = $tokenData->token->expires_at->diffInSeconds(Carbon::now());

        return response()->json([
            'access_token' => $token,
            'token_type' => 'Bearer',
            'expires_in' => $expiration
        ]);
    }

    public function getUser()
    {
        return response()->json(auth()->user());
    }

    public function logout()
    {
        $token = auth()->user()->token();

        /* --------------------------- revoke access token -------------------------- */
        $token->revoke();
        $token->delete();

        /* -------------------------- revoke refresh token -------------------------- */
        $refreshTokenRepository = app(RefreshTokenRepository::class);
        $refreshTokenRepository->revokeRefreshTokensByAccessTokenId($token->id);

        return response()->json(['message' => 'Logged out successfully']);
    }

    /* ----------------- get both access_token and refresh_token ---------------- */
    public function loginGrant(Request $request)
    {
        $validator = Validator::make($request->all(), [
            'email' => 'required|email',
            'password' => 'required'
        ]);

        if ($validator->fails()) {
            return response()->json($validator->errors(), 400);
        }

        $baseUrl = url('/');
        $response = Http::post("{$baseUrl}/oauth/token", [
            'username' => $request->email,
            'password' => $request->password,
            'client_id' => config('passport.password_grant_client.id'),
            'client_secret' => config('passport.password_grant_client.secret'),
            'grant_type' => 'password'
        ]);

        $result = json_decode($response->getBody(), true);
        if (!$response->ok()) {
            return response()->json(['error' => 'Unauthorized'], 401);
        }
        return response()->json($result);
    }

    /* -------------------------- refresh access_token -------------------------- */
    public function refreshToken(Request $request)
    {
        $validator = Validator::make($request->all(), [
            'refresh_token' => 'required'
        ]);

        if ($validator->fails()) {
            return response()->json($validator->errors(), 400);
        }

        $baseUrl = url('/');
        $response = Http::post("{$baseUrl}/oauth/token", [
            'refresh_token' => $request->refresh_token,
            'client_id' => config('passport.password_grant_client.id'),
            'client_secret' => config('passport.password_grant_client.secret'),
            'grant_type' => 'refresh_token'
        ]);

        $result = json_decode($response->getBody(), true);
        if (!$response->ok()) {
            return response()->json(['error' => $result['error_description']], 401);
        }
        return response()->json($result);
    }
}

14. Go to app/Providers and open RouteServiceProvider.php. uncomment $namespace;

protected $namespace = 'App\\Http\\Controllers';

15. Go to app/Http/Middleware and open Authenticate.php. After that update redirectTo()  method.

<?php

namespace App\Http\Middleware;

use Illuminate\Auth\Middleware\Authenticate as Middleware;

class Authenticate extends Middleware
{
    /**
     * Get the path the user should be redirected to when they are not authenticated.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return string|null
     */
    protected function redirectTo($request)
    {
        if ($request->is('api/*') || $request->is('oauth/*')) {
            return route('unauthorized');
        }
        if (! $request->expectsJson() ) {
            return route('login');
        }
    }
}

Note:- In the case of Unauthenticated(API) instead of redirecting to the login page we are redirecting to unauthorized route for the custom JSON response.

16. Go to routes folder and update api.php

<?php

use Illuminate\Support\Facades\Route;

Route::group(['prefix' => 'auth', 'namespace' => 'Api'], function () {

    Route::post('register',     'AuthController@register');

    /* ------------------------ For Personal Access Token ----------------------- */
    Route::post('login',        'AuthController@login');
    /* -------------------------------------------------------------------------- */

    Route::group(['middleware' => 'auth:api'], function () {
        Route::get('logout',    'AuthController@logout');
        Route::get('user',      'AuthController@getUser');
    });

   /* ------------------------ For Password Grant Token ------------------------ */
    Route::post('login_grant',  'AuthController@loginGrant');
    Route::post('refresh',      'AuthController@refreshToken');
    /* -------------------------------------------------------------------------- */

    /* -------------------------------- Fallback -------------------------------- */
    Route::any('{segment}', function () {
        return response()->json([
            'error' => 'Invalid url.'
        ]);
    })->where('segment', '.*');
});

Route::get('unauthorized', function () {
    return response()->json([
        'error' => 'Unauthorized.'
    ], 401);
})->name('unauthorized');

Finally Open Postman or any REST API Client and test these API's

Checkout my full laravel-passport example.

https://github.com/ultimateakash/laravel-passport

If you need any help, don't hesitate to comment below.

Thanks.

Leave Your Comment