DEV Community

Cover image for Laravel Passport: API authentication with access and refresh token
M H Hasib
M H Hasib

Posted on • Edited on

Laravel Passport: API authentication with access and refresh token

In Laravel, we can use authentication systems like Sanctum, and Passport. Still, Passport is one of the most popular authentication systems in Laravel. Most of the developer like to do authentication using Laravel Passport because of its extraordinary features. Like if you want to authenticate machine to machine, Passport is one of the favorite tools to use. So without a further delay, let's jump into code.

In this tutorial, we are using the latest version of Laravel which is Laravel 11.

First of all, let's create a project in Laravel using the following command.

composer create-project laravel/laravel passport-authentication
Enter fullscreen mode Exit fullscreen mode

So, we created a brand new Laravel project. Now if you go to the route directory will not find any api.php file. Because, from Laravel 11 API will not come by default. If we want API we need to install it manually. Before setting up Laravel Passport, make sure you set up your .env file with your database and other credentials. Now migrate the database using the following command.

php artisan migrate
Enter fullscreen mode Exit fullscreen mode

Now let's install Laravel Passport using the following command.

php artisan install:api --passport
Enter fullscreen mode Exit fullscreen mode

So we have installed Laravel Passport successfully. Now we have to set up the model you want to connect for authentication. Let's prepare our model. I am using the User model for the authentication.

We have to use the HasApiTokens trait in the user model. Also, need to import the namespace Laravel\Passport\HasApiTokens at the top. So here is our User model.

<?php

namespace App\Models;

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;
}
Enter fullscreen mode Exit fullscreen mode

Now go to the config/auth.php file and you will find the guard array like this.

'guards' => [
        'web' => [
            'driver' => 'session',
            'provider' => 'users',
        ],
    ],
Enter fullscreen mode Exit fullscreen mode

Now we have to modify it and need to add another guard in the array named api whose driver should be passport and whose provider should be users as we are using users as our authentication table. Now the guards array should look like this.

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

    'api' => [
        'driver' => 'passport',
        'provider' => 'users',
    ],
],
Enter fullscreen mode Exit fullscreen mode

Now When deploying Passport to your application's servers for the first time, you will likely need to run the passport:keys command. This command generates the encryption keys Passport needs in order to generate access tokens. The generated keys are not typically kept in source control:

php artisan passport:keys
Enter fullscreen mode Exit fullscreen mode

Now, open AppServiceProvider and add this code Passport::enablePasswordGrant(); to the boot method. And your AppServiceProvider should look like this.

<?php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use Laravel\Passport\Passport;

class AppServiceProvider extends ServiceProvider
{
    /**
     * Register any application services.
     */
    public function register(): void
    {
        //
    }

    /**
     * Bootstrap any application services.
     */
    public function boot(): void
    {
        Passport::enablePasswordGrant();
    }
}
Enter fullscreen mode Exit fullscreen mode

Now we need to create password grant client using this command

php artisan passport:client --password
Enter fullscreen mode Exit fullscreen mode

It will generate a password secret id and a secret key. You need to add these code in your .env file like this.

APP_NAME=Laravel
APP_ENV=local
APP_KEY=base64:tq7VnODMFUn5CLAaaiXEy338CS9LxSr4RxPrvC+q1WA=
APP_DEBUG=true
APP_TIMEZONE=UTC
APP_URL=http://passport-authentication.test

APP_LOCALE=en
APP_FALLBACK_LOCALE=en
APP_FAKER_LOCALE=en_US

APP_MAINTENANCE_DRIVER=file
APP_MAINTENANCE_STORE=database

BCRYPT_ROUNDS=12

LOG_CHANNEL=stack
LOG_STACK=single
LOG_DEPRECATIONS_CHANNEL=null
LOG_LEVEL=debug

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=passport_authentication
DB_USERNAME=root
DB_PASSWORD=

SESSION_DRIVER=database
SESSION_LIFETIME=120
SESSION_ENCRYPT=false
SESSION_PATH=/
SESSION_DOMAIN=null

BROADCAST_CONNECTION=log
FILESYSTEM_DISK=local
QUEUE_CONNECTION=database

CACHE_STORE=database
CACHE_PREFIX=

MEMCACHED_HOST=127.0.0.1

REDIS_CLIENT=phpredis
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379

MAIL_MAILER=log
MAIL_HOST=127.0.0.1
MAIL_PORT=2525
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=null
MAIL_FROM_ADDRESS="hello@example.com"
MAIL_FROM_NAME="${APP_NAME}"

AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
AWS_DEFAULT_REGION=us-east-1
AWS_BUCKET=
AWS_USE_PATH_STYLE_ENDPOINT=false

VITE_APP_NAME="${APP_NAME}"


PASSPORT_PASSWORD_CLIENT_ID=9c1a21e3-307b-473e-a325-bd14ac0fdbc2
PASSPORT_PASSWORD_SECRET=VuFvz2GNiwJrRQMyF9FZLg9r4vb0zYXwhQMYH4NJ
Enter fullscreen mode Exit fullscreen mode

Look at the bottom of this .env file. I have added two variables named PASSPORT_PASSWORD_CLIENT_ID and PASSPORT_PASSWORD_SECRET.

Now, we are ready to create our route and controller code. Now go to the routes/api.php and create these routes. Here our routes should look like this.

<?php

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\Api\AuthController;

Route::post('/login', [AuthController::class, 'login']);
Route::post('/register', [AuthController::class, 'register']);
Route::post('/refresh', [AuthController::class, 'refreshToken']);

Route::group(['middleware' => ['auth:api']], function () {
    Route::get('/me', [AuthController::class, 'me']);
    Route::post('/logout', [AuthController::class, 'logout']);

});
Enter fullscreen mode Exit fullscreen mode

Now create a controller in Api folder using following this command.

php artisan make:controller Api/AuthController
Enter fullscreen mode Exit fullscreen mode

Now open the AuthController.php and replace this code in this code.

<?php

namespace App\Http\Controllers\Api;

use App\Http\Controllers\Controller;
use App\Http\Requests\LoginRequest;
use App\Http\Requests\RefreshTokenRequest;
use App\Http\Requests\RegisterRequest;
use App\Models\User;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Http;

class AuthController extends Controller
{
    /**
     * User registration
     */
    public function register(RegisterRequest $request): JsonResponse
    {
        $userData = $request->validated();

        $userData['email_verified_at'] = now();
        $user = User::create($userData);

        $response = Http::post(env('APP_URL') . '/oauth/token', [
            'grant_type' => 'password',
            'client_id' => env('PASSPORT_PASSWORD_CLIENT_ID'),
            'client_secret' => env('PASSPORT_PASSWORD_SECRET'),
            'username' => $userData['email'],
            'password' => $userData['password'],
            'scope' => '',
        ]);

        $user['token'] = $response->json();

        return response()->json([
            'success' => true,
            'statusCode' => 201,
            'message' => 'User has been registered successfully.',
            'data' => $user,
        ], 201);
    }

    /**
     * Login user
     */
    public function login(LoginRequest $request): JsonResponse
    {
        if (Auth::attempt(['email' => $request->email, 'password' => $request->password])) {
            $user = Auth::user();

            $response = Http::post(env('APP_URL') . '/oauth/token', [
                'grant_type' => 'password',
                'client_id' => env('PASSPORT_PASSWORD_CLIENT_ID'),
                'client_secret' => env('PASSPORT_PASSWORD_SECRET'),
                'username' => $request->email,
                'password' => $request->password,
                'scope' => '',
            ]);

            $user['token'] = $response->json();

            return response()->json([
                'success' => true,
                'statusCode' => 200,
                'message' => 'User has been logged successfully.',
                'data' => $user,
            ], 200);
        } else {
            return response()->json([
                'success' => true,
                'statusCode' => 401,
                'message' => 'Unauthorized.',
                'errors' => 'Unauthorized',
            ], 401);
        }

    }

    /**
     * Login user
     *
     * @param  LoginRequest  $request
     */
    public function me(): JsonResponse
    {

        $user = auth()->user();

        return response()->json([
            'success' => true,
            'statusCode' => 200,
            'message' => 'Authenticated use info.',
            'data' => $user,
        ], 200);
    }

    /**
     * refresh token
     *
     * @return void
     */
    public function refreshToken(RefreshTokenRequest $request): JsonResponse
    {
        $response = Http::asForm()->post(env('APP_URL') . '/oauth/token', [
            'grant_type' => 'refresh_token',
            'refresh_token' => $request->refresh_token,
            'client_id' => env('PASSPORT_PASSWORD_CLIENT_ID'),
            'client_secret' => env('PASSPORT_PASSWORD_SECRET'),
            'scope' => '',
        ]);

        return response()->json([
            'success' => true,
            'statusCode' => 200,
            'message' => 'Refreshed token.',
            'data' => $response->json(),
        ], 200);
    }

    /**
     * Logout
     */
    public function logout(): JsonResponse
    {
        Auth::user()->tokens()->delete();

        return response()->json([
            'success' => true,
            'statusCode' => 204,
            'message' => 'Logged out successfully.',
        ], 204);
    }
}

Enter fullscreen mode Exit fullscreen mode

Now, create three request file step by step.

RegisterRequest:

 php artisan make:request RegisterRequest
Enter fullscreen mode Exit fullscreen mode

Now replace this code in this file.

<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class RegisterRequest extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     */
    public function authorize(): bool
    {
        return true;
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
     */
    public function rules(): array
    {
        return [
            'name' => ['required', 'max:255'],
            'email' => ['required', 'email', 'unique:users'],
            'password' => ['required', 'min:8', 'confirmed'],
        ];
    }
}
Enter fullscreen mode Exit fullscreen mode

LoginRequest:

 php artisan make:request LoginRequest
Enter fullscreen mode Exit fullscreen mode

Now replace this code in this file.

<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class LoginRequest extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     */
    public function authorize(): bool
    {
        return true;
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
     */
    public function rules(): array
    {
        return [
            'email' => 'email|required',
            'password' => 'required',
        ];
    }
}
Enter fullscreen mode Exit fullscreen mode

RefreshTokenRequest:

 php artisan make:request RefreshTokenRequest
Enter fullscreen mode Exit fullscreen mode

Now replace this code in this file.

<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class RefreshTokenRequest extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     */
    public function authorize(): bool
    {
        return true;
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
     */
    public function rules(): array
    {
        return [
            'refresh_token' => ['required', 'string'],
        ];
    }
}
Enter fullscreen mode Exit fullscreen mode

One more suggestion:

Sometimes you might need to change the refresh token and access token validity. For these changes you can add the following code in the boot method of AppServiceProvider.

Passport::tokensExpireIn(now()->addDays(15));
Passport::refreshTokensExpireIn(now()->addDays(30));
Passport::personalAccessTokensExpireIn(now()->addMonths(6));
Enter fullscreen mode Exit fullscreen mode

Then your AppServiceProvider should look like this.

<?php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use Laravel\Passport\Passport;

class AppServiceProvider extends ServiceProvider
{
    /**
     * Register any application services.
     */
    public function register(): void
    {
        //
    }

    /**
     * Bootstrap any application services.
     */
    public function boot(): void
    {
        Passport::tokensExpireIn(now()->addDays(15));
        Passport::refreshTokensExpireIn(now()->addDays(30));
        Passport::personalAccessTokensExpireIn(now()->addMonths(6));
        Passport::enablePasswordGrant();
    }
}
Enter fullscreen mode Exit fullscreen mode

Congrats. You completed all the steps and HERE WE GO. You are ready to test your API.

You will get all the code in this git repository for your help.

Suggested Reads

Top comments (8)

Collapse
 
masumbillah profile image
Md. Masum Billah

Nice content.

Collapse
 
mahmudulhsn profile image
M H Hasib

Thank you so much bhai <3

Collapse
 
21cse1014karthik_allur_3 profile image
21CSE1014-Karthik Allur

where is json token created

Collapse
 
landtrust profile image
Landtrust

Cool

Collapse
 
mahmudulhsn profile image
M H Hasib

Thanks a lot <3

Collapse
 
dean871025 profile image
din abu

Working like charms!

Collapse
 
sabinshrestha8 profile image
Sabin Shrestha

its taking lots of time and throwing it exceeded the timelimit but its updating in my db.

how to fix this?

Collapse
 
hctor_ayestarn_9c0e3c82 profile image
Héctor Ayestarán

Password Grant is legacy. It is discouraged by Laravel:
laravel.com/docs/11.x/passport#pas...