Laravel 8 REST API with Passport Authentication
Mohamad's interest is in Programming (Mobile, Web, Database and Machine Learning). He is studying at the Center For Artificial Intelligence Technology (CAIT), Universiti Kebangsaan Malaysia (UKM).
[1] Create a project.
Example:
Laravel version=8
Project name=lara8passport
[2] Add package laravel/passport.
composer require laravel/passport
Output example:

The package laravel/passport is added to the composer.json file.
[3] Migrate database.
php artisan migrate
Output example:

Notice that some oauth-related tables have been migrated.
[4] Install laravel/passport.
php artisan passport:install
Output example:

Note: Keep the following details in a secure place.
Client ID: 1
Client secret: tXll1sTSr8QCCPGNHXFBrxnIxIJLjVO4DNGtE7xe
Client ID: 2
Client secret: pV7YnUH2K4AcHHjnDbtJan0qRGDsx5HBYIXpXp2T
[5] Update Environment Variables.
(Update the .env file with data from step 4)
PASSPORT_PERSONAL_ACCESS_CLIENT_ID=1
PASSPORT_PERSONAL_ACCESS_CLIENT_SECRET=TViBskWZuyQA700di3oxhtib7pX1JXpaoHSPuSIy
PASSPORT_PASSWORD_GRANT_CLIENT_ID=2
PASSPORT_PASSWORD_GRANT_CLIENT_SECRET=iTVGhmSIprC26iFmkCyQsosF9xUe8TznpyjcHsZB
[6] Publish Passport configuration.
php artisan vendor:publish --tag=passport-config
Outcome: The file config/passport.php is created.
[7] Edit Passport configuration.
(Update/Add the following lines in config/passport.php)
The personal_access_client entry already existed. Check that it tallies with the above data.
The password_grant_client entry does not exist yet. Add the entry.
/*
|--------------------------------------------------------------------------
| Personal Access Client
|--------------------------------------------------------------------------
*/
'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] Update User Model.
(Update App/Models/User.php)
Remove "use Laravel\Sanctum\HasApiTokens;"
Insert "use Laravel\Passport\HasApiTokens;"
<?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;
/**
* The attributes that are mass assignable.
*
* @var array<int, string>
*/
protected $fillable = [
'name',
'email',
'password',
];
/**
* The attributes that should be hidden for serialization.
*
* @var array<int, string>
*/
protected $hidden = [
'password',
'remember_token',
];
/**
* The attributes that should be cast.
*
* @var array<string, string>
*/
protected $casts = [
'email_verified_at' => 'datetime',
];
}
[9] Update Auth Guard.
(Update config/auth.php)
- Set 'driver' => 'passport'.
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
'api' => [
'driver' => 'passport',
'provider' => 'users',
],
],
[10] Update AuthServiceProvider.
(Update app/Providers/AuthServiceProvider.php.php)
Add "use Laravel\Passport\Passport;"
Uncomment ModelPolicy.
Add Passport::routes() in boot method
<?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));
}
}
[11] Create AuthController
- Run Artisan command
php artisan make:controller Api/AuthController
- Edit codes (in App/Http/Controllers/Api/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('//localhost');
$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('//localhost');
$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);
}
}
[12] Edit Route Service Provider.
(Edit App/Providers/RouteServiceProvider.php)
- Uncomment $namespace.
protected $namespace = 'App\\Http\\Controllers';
[13] Update Redirection.
(Edit App/Http/Middleware/Authenticate.php)
- Update the redirectTo() function.
<?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');
}
}
}
[14] Update Routes.
(Edit in Routes/Api.php)
Define the routes for the following endpoints:
register
login
logout
user
login_grant
refresh
unauthorized
<?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');
[15] Test Endpoints.
[1] Register
curl -X POST https://mugxo.ciroue.com/api/auth/register `
-H 'Content-Type: application/x-www-form-urlencoded' `
-H 'Accept: application/json' `
-d 'name=saya' `
-d 'email=saya@gmail.com' `
-d 'password=Abcd1234'
[2] Login
curl -X POST https://mugxo.ciroue.com/api/auth/login `
-H 'Content-Type: application/x-www-form-urlencoded' `
-H 'Accept: application/json' `
-d 'email=saya@gmail.com' `
-d 'password=Abcd1234'
[3] User
curl -X GET https://mugxo.ciroue.com/api/auth/user `
-H 'Content-Type: application/x-www-form-urlencoded' `
-H 'Authorization: Bearer <access_token>'
[4] Login Grant
curl -X POST https://mugxo.ciroue.com/api/auth/login_grant `
-H 'Content-Type: application/x-www-form-urlencoded' `
-H 'Accept: application/json' `
-d 'email=saya@gmail.com' `
-d 'password=Abcd1234'
[5] Refresh
curl -X POST https://mugxo.ciroue.com/api/auth/refresh `
-H 'Content-Type: application/x-www-form-urlencoded' `
-H 'Accept: application/json' `
-d 'refresh_token=<refresh_token>'
.