Laravel 12 API Authentication using Passport Tutorial with Example

Learn how to set up secure Rest API authentication in Laravel 12 using Passport. Step-by-step guide to OAuth2, token management, and protecting routes. Perfect for developers building robust APIs!

Laravel 12 API Authentication using Passport Tutorial with Example Image

APIs are the backbone of modern web applications, and securing them is crucial. Laravel, one of the most popular PHP frameworks, provides a powerful package called Laravel Passport to handle API authentication seamlessly. In this blog post, we’ll walk through how to set up Rest API authentication in Laravel 12 using Passport.

What is Laravel Passport?

Laravel Passport is an OAuth2 server implementation for Laravel. It provides a full OAuth2 server implementation for your Laravel application, allowing you to issue access tokens and authenticate API requests. Passport is built on top of the League OAuth2 Server and is a great choice for securing your APIs.

Prerequisites

Before we dive into the implementation, make sure you have the following:

  1. Laravel 12 installed on your local machine.
  2. A database configured in your .env file.
  3. Basic knowledge of Laravel (routes, controllers, migrations, etc.).

Step 1: Install Laravel Passport

To get started, you need to install Passport via Composer. 

Run the following command in your terminal:

php artisan install:api --passport

Step 2: Run Passport Migrations

Passport requires a database table to store API tokens. Run the following command to create the necessary migration:

php artisan migrate

Step 3: Configure Authentication Guards

In config/auth.php, set the API driver to passport:

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

	'api' => [
		'driver' => 'passport',
		'provider' => 'users',
	],
],

Step 4: Configure User Model to Use Passport

Next, you need to update your User model to use the HasApiTokens trait provided by Passport. 

Open the app/Models/User.php file and add the following:

use Laravel\Passport\HasApiTokens;

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

This trait allows the user model to issue API tokens.

Step 5: Create Routes

Now, let’s create routes for user registration, login, and token-based authentication. Open the routes/api.php file and add the following routes:

<?php

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

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

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

    Route::prefix('a_products')->group(function () {
        Route::get('/', [ProductController::class, 'index']);
        Route::post('save', [ProductController::class, 'save']);
        Route::get('{id}/show', [ProductController::class, 'show']);
        Route::patch('{id}/update', [ProductController::class, 'update']);
        Route::delete('{id}/delete', [ProductController::class, 'delete']);
    });
});

Here’s what each route does:

  • /register: Handles user registration.
  • /login: Handles user login and returns an API token.
  • /logout: Logs the user out and revokes the token.
  • /user: Returns the authenticated user’s details.
  • /a_products: Returns a list of all products.
  • /a_products/save: Creates a new product with the provided details.
  • /a_products/{id}/show: Returns the details of a specific product based on the provided id.
  • /a_products/{id}/update: Updates the details of a specific product based on the provided id.
  • /a_products/{id}/delete: Deletes a specific product based on the provided id.

Step 6: Create the UserResource File

Next, create a UserResource to format the JSON response for your API. Run the following Artisan command:

php artisan make:resource UserResource

This command will generate a new file at app/Http/Resources/UserResource.php. Open the newly created UserResource.php file located in app/Http/Resources. Update the toArray method to customize the JSON response for the User model. For example:

public function toArray(Request $request): array
{
	return [
		'id' => $this->id,
		'name' => $this->name,
		'email' => $this->email,
		'email_verified_at' => $this->email_verified_at,
	];
}

Step 7: Create the AuthController

Next, create a controller to handle authentication logic. Run the following command:

php artisan make:controller Api\AuthController

Now, open the app/Http/Controllers/Api/AuthController.php file and add the following methods:

<?php

namespace App\Http\Controllers\Api;

use App\Http\Controllers\Controller;
use App\Http\Requests\Api\CreateUserRequest;
use App\Http\Requests\Api\LoginRequest;
use App\Http\Resources\UserResource;
use App\Models\User;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;

class AuthController extends Controller
{
    public function register(CreateUserRequest $request): JsonResponse
    {
        $data = $request->validated();
        $data['password'] = Hash::make($data['password']);

        User::query()->create($data);

        return response()->json(['message' => 'User registerd successfully'], 201);
    }

    public function login(LoginRequest $request): JsonResponse
    {
        $data = $request->validated();

        if (Auth::attempt(['email' => $request->email, 'password' => $request->password])) {
            $user = Auth::user();
            $token = $user->createToken('authToken')->accessToken;

            return response()->json(['token' => $token], 200);
        }

        return response()->json(['message' => 'The provided credentials are incorrect.'], 401);
    }

    public function user(): UserResource
    {
        $user = Auth::user();

        return new UserResource($user);
    }

    public function logout(Request $request): JsonResponse
    {
        $request->user()->token()->revoke();

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

Next, Create the Form request file to handle the incoming request. Run following command to create file

php artisan make:request Api\CreateUserRequest
php artisan make:request Api\LoginRequest

Update app/Http/Request/Api/CreateUserRequest.php File:

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
{
	$rules = [
		'name' => ['required', 'string', 'max:255'],
		'email' => ['required', 'email', 'unique:users', 'max:255'],
		'password' => ['required', 'string', 'min:8', 'max:255', 'confirmed'],
	];

	return $rules;
}

Update app/Http/Request/Api/LoginRequest.php File:

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
{
	$rules = [
		'email' => ['required', 'email', 'max:255'],
		'password' => ['required', 'min:8'],
	];

	return $rules;
}

Step 8: Create the Product Migration File

Now for handle the product CRUD create the migration file. Run the following command:

php artisan make:migration create_products_table

Update Migration File:

public function up(): void
{
	Schema::create('products', function (Blueprint $table) {
		$table->id();
		$table->string('name');
		$table->text('description');
		$table->decimal('price');
		$table->string('image');
		$table->timestamps();
	});
}

Note: Don't forgot to run migration for create product table.

Step 9: Create the Product Model File:

Run following Command for create the Product model file:

php artisan make:model Product

Open the Product model at app/Models/Product.php and update it with the following code:

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Product extends Model
{
    protected $guarded = [];
}

$guarded empty array allow all filed fillable

Step 10: Create the ProductResource File

Next, create a ProductResource to format the JSON response for your API. Run the following Artisan command:

php artisan make:resource ProductResource

This command will generate a new file at app/Http/Resources/ProductResource.php. Open the newly created ProductResource.php file located in app/Http/Resources. Update the toArray method to customize the JSON response for the Product model. For example:

public function toArray(Request $request): array
{
	return [
		'name' => $this->name,
		'description' => $this->description,
		'price' => $this->price,
		'image' => asset('storage/product/'.$this->image),
	];
}

Step 11: Create the ProductController

Next, create a controller to handle Product CRUD logic. Run the following command:

php artisan make:controller Api\ProductController

Now, open the app/Http/Controllers/Api/ProductController.php file and add the following methods:

<?php

namespace App\Http\Controllers\Api;

use App\Helper\CommonHelper;
use App\Http\Controllers\Controller;
use App\Http\Requests\Api\CreateProductRequest;
use App\Http\Resources\ProductResource;
use App\Models\Product;
use Illuminate\Http\JsonResponse;

class ProductController extends Controller
{
    public function index()
    {
        $products = Product::query()->paginate(5);

        return ProductResource::collection($products);
    }

    public function save(CreateProductRequest $request): JsonResponse
    {
        $data = $request->validated();
        $data['image'] = CommonHelper::uploadFile($request->file('image'), 'product');
        Product::query()->create($data);

        return response()->json(['message' => 'Product create successfuly'], 200);
    }

    public function show($id): JsonResponse|ProductResource
    {
        $product = Product::query()->find($id);
        if (! empty($product)) {
            return new ProductResource($product);
        }

        return response()->json(['message' => 'Product not found'], 404);
    }

    public function update($id, CreateProductRequest $request): JsonResponse
    {
        $data = $request->validated();

        $product = Product::query()->where('id', $id)->first();
        if (! empty($product)) {
            if ($request->hasFile('image')) {
                $data['image'] = CommonHelper::uploadFile($request->file('image'), 'product', $product->image);
            }
            $product->update($data);

            return response()->json(['message' => 'Product update successfully'], 200);
        }

        return response()->json(['message' => 'Product not found'], 404);
    }

    public function delete($id): JsonResponse
    {
        $product = Product::where('id', $id)->first();

        if (! empty($product)) {
            CommonHelper::removeOldFile('public/product/'.$product->image);
            $product->delete();

            return response()->json(['message' => 'Product delete successfully'], 200);
        }

        return response()->json(['message' => 'Product not found'], 404);
    }
}

Next, Create the Form request file to handle the incoming request. Run following command to create file

php artisan make:request Api\CreateProductRequest

Update app/Http/Request/Api/CreateProductRequest.php File:

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
{
	$rules = [
		'name' => ['required', 'string', 'max:255'],
		'description' => ['required', 'string'],
		'price' => ['required', 'numeric'],
		'image' => ['required', File::types(['jpeg', 'jpg', 'png', 'webp'])->max(1 * 500)],
	];

	if (in_array($this->method(), ['PUT', 'PATCH'])) {
		$rules['name'][0] = 'sometimes';
		$rules['description'][0] = 'sometimes';
		$rules['price'][0] = 'sometimes';
		$rules['image'] = array_merge(['sometimes'], CommonHelper::getFileValidationRule('image', ['jpeg', 'jpg', 'png', 	'webp'], (1 * 500)));
	}

	return $rules;
}

Step 12: Create CommonHelper File

Create CommonHelper.php file in app\Helper directory.

update the app\Helper\CommonHelper.php file:

<?php

namespace App\Helper;

use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Storage;
use Illuminate\Validation\Rules\File;

class CommonHelper
{
    public static function uploadFile(UploadedFile $file, $path, $oldFile = ''): string
    {
        if (! empty($file)) {
            // Remove Old file
            if (! empty($oldFile)) {
                Storage::delete('public/'.$path.'/'.$oldFile);      // Delete file from local
            }

            // Upload image
            $path = $file->store('public/'.$path);
            $parts = explode('/', $path);

            return end($parts);
        }

        return '';
    }

    public static function removeOldFile($oldFile): void
    {
        // Remove Old file
        if (! empty($oldFile)) {
            Storage::delete($oldFile);                      // Delete file from local
        }
    }

    public static function getFileValidationRule(string $key, $types, $size = (1 * 500)): array
    {
        if (request()->hasFile($key)) {
            return [File::types($types)->max($size)];
        }

        return ['string'];
    }
}

Step 13: Change File storing path private to public

Change the value of key drive in 'local' array in disk array storage_path('app/private') to storage_path('app').

'local' => [
	'driver' => 'local',
	'root' => storage_path('app'),
	'serve' => true,
	'throw' => false,
	'report' => false,
],

now link storage to public directory.

php artisan storage:link

Step 14: Test Your API

Now that your API is set up, it’s time to test it! You can use tools like Postman or cURL to send requests to your API endpoints and verify their functionality.

  1. Register a User
    • Endpoint: POST /api/register
    • Body:

      {
        "name": "Test User",
        "email": "test@gmail.com",
        "password": "password",
        "password_confirmation": "password"
      }
  2. Login
    • Endpoint: POST /api/login
    • Body:

      {
        "email": "test@gmail.com",
        "password": "password"
      }
  3. Access Authenticated Route
    • Endpoint: GET /api/user
    • Header:

      Authorization: Bearer your-api-token
  4. Logout
    • Endpoint: POST /api/logout
    • Header:

      Authorization: Bearer your-api-token

Product CRUD Operations

  1. List All Products
    • Endpoint: GET /api/a_products
    • Header:

      Authorization: Bearer your-api-token
  2. Create a New Product
    • Endpoint: POST /api/a_products/save
    • Header:

      Authorization: Bearer your-api-token
    • Body:

      {
        "name": "Test Product",
        "description": "Description of the product without any length limitation.",
        "price": 200,
        "image": "anyimage.jpg"
      }
  3. Show Product Details
    • Endpoint: GET /api/a_products/{id}/show
    • Header:

      Authorization: Bearer your-api-token
  4. Update a Product
    • Endpoint: PATCH /api/a_products/{id}/update
    • Header:

      Authorization: Bearer your-api-token
    • Body:

      {
        "name": "Updated Product Name",
        "description": "Updated description of the product.",
        "price": 250,
        "image": "updatedimage.jpg"
      }
    • Note: If the PATCH method doesn’t work in your API call, you can use POST with a hidden input _method='PATCH'.
  5. Delete a Product
    • Endpoint: DELETE /api/a_products/{id}/delete
    • Header:

      Authorization: Bearer your-api-token

Testing Tips

  • Use Postman or any API testing tool to send requests to the endpoints.
  • Ensure you include the Authorization header with the Bearer token for authenticated routes.
  • Verify the responses to confirm that your API is working as expected.

By following these steps, you can thoroughly test your API and ensure that all endpoints are functioning correctly. This will help you identify and fix any issues before deploying your application.

Conclusion

Congratulations! You have successfully set up secure API authentication in Laravel 12 using Laravel Passport. This authentication system ensures that API requests are properly secured, preventing unauthorized access. Laravel Passport makes it simple to implement OAuth2 authentication in your Laravel projects, providing robust security for modern web applications.

authentication.

For more Laravel tutorials and API authentication guides, follow this blog and stay updated. Happy coding! 🚀

Do you Like?