Laravel 8 REST API with Passport Authentication
[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>'
.