DEV Community

Kingsconsult
Kingsconsult

Posted on • Edited on

How to Create a Secure CRUD RESTful API in Laravel 8 and 7 Using Laravel Passport

Good day, today we are going to be coding strictly in the backend, we are going to convert our Laravel 8 CRUD app to RESTful APIs. API is a software intermediary that allows two applications to talk to each other. Somethings you might need to create an app that can be run on different languages or frameworks, for example, you can use Laravel to create the backend for your application while the frontend might run on any JavaScript frameworks. API allows two or more programs to communicate with each other.
There are different types of APIs, but today we are going to concentrate on RESTful APIs. REST stands for Representational State Transfer, while API stands for Application Programming Interface. You can read more about API from the internet or other programming material.

Click on my profile to follow me to get more updates.

Normally, when we build an application, we need to secure the application so that unauthorized access will be blocked. Laravel already makes it easy to perform authentication via traditional login forms, but what about APIs? API does not maintain a session state between requests, so there is a need to use tokens to authenticate the app and also authorize the usage of the app.
Laravel provided a package that makes authentication of API very easy by using Laravel Passport, though, there are other options, this is the official packages by the Laravel team, this package provides a full OAuth2 server implementation. It is easy to apply and can be achieved in minutes. Without saying much, let's dive into it.

STEP 1: install laravel 8

To install the latest laravel framework, which is laravel 8.0 as of the time of publishing this article, run the command below

composer create-project --prefer-dist laravel/laravel laravel_8_api_crud

This will automatically create a laravel 8 app and some couple of things have been set up, we don’t need to copy and rename the env.example file, Laravel 8 does that automatically for us
alt text
Another important thing about Laravel 8, you don’t need to generate APP_KEY, this new version will also generate it for us.
alt text
With that all set up, our app is ready.

Step 2: Database setup

Create an empty database, Open the .env file, and update your database configurations.
alt text
For more information on this step, you can visit my previous article, where I explained the steps very well, Laravel 8 CRUD.

Step 3: Install Laravel Passport

let us install Laravel Passport, Passport service provider registers its own database migration directory, this means that it creates the table that we will be needing for storing clients. The table will be used to store the token generated which will be used to identify a currently authenticated user. This token will then be attached to every request allowing each user access to protected routes.

composer require laravel/passport

install Laravel/passport
After installation, then we need to migrate, but before we run our migration command, we need to specify the default string length, else, we are going to run into errors. So go to app/Providers/AppServiceProvider.php and add this to the boot function

Schema::defaultstringLength(191);

also, add this to the top of the class

use Illuminate\Support\Facades\Schema;

alt text
Finally, we run our migration command

php artisan migrate

migrations

Step 4: Create the encryption keys

We need to create the encryption keys that are needed to generate our secure access tokens.

php artisan passport:install

This command will also create "personal access" and "password grant" clients which will be used to generate access tokens.
install passport

Step 5: Add the HasApiTokens trait to our user model

Go to App\Models\User.php and tell the User class to

use HasApiTokens,

Also add this to the top

use Laravel\Passport\HasApiTokens;

User Model

Step 6: Call the passport routes in AuthServiceProvider

Go to App/Providers/AuthServiceProvider.php and add

Passport::routes();

To the boot method, also add the path before the class at the top

use Laravel\Passport\Passport;

Uncomment the policy in the protected method of $policies
AuthServiceProvider

Step 7: Set the driver

This will be the final step in the setting up and configuration of Laravel\Passport, we going to change our api driver from the default token to passport.
Go to config\auth.php and locate the guards array. In the api key, change the driver from token to passport
auth config

Step 8: Create the Migration file for our CRUD api project

php artisan make:model Project -m

make migration
A migration file will be created in the database/migrations folder, and we need to create our schema, I added name (string), introduction (string), location (string), cost of the project (integer).



<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateProjectsTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('projects', function (Blueprint $table) {
            $table->id();
            $table->string('name', 255);
            $table->string('introduction', 500)->nullable();
            $table->string('location', 255)->nullable();
            $table->integer('cost')->nullable();
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('projects');
    }
}


Enter fullscreen mode Exit fullscreen mode

We need to update our Project Model so that it can be able to accept the fields. We need to add a protected $fillable method that will contain the fields that a user of the app can fill, this helps to prevent someone from hacking into the app through input fields.



<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Project extends Model
{
    use HasFactory;

    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = [
        'name',
        'location',
        'introduction',
        'cost',
    ];


    /**
     * The attributes that should be cast to native types.
     *
     * @var array
     */
    protected $casts = [
        'cost' => 'int',
    ];
}


Enter fullscreen mode Exit fullscreen mode

Step 9: Migrate the new table

migration

Step 10: Create a Resource

When building an API, the response will always be in JSON format, so we need a transformation layer that sits between our Eloquent models and the JSON responses. This is what will serve the response to the application’s user in a JSON format. Laravel provides us with a resource class that will help in transforming our models and the model collections into JSON. So we are going to create that

php artisan make:resource ProjectResource

This will create a folder in the app directory called Resources and also a file ProjectResource.php inside the resources.
create Project Resource

Step 11: Create our Controllers

The Controller is responsible for the direction of the flow of data and an interface between the user and the database and views. In this case, we are not interacting with views now because we are dealing with API, so our response will be in JSON format. The standard for RESTful APIs is to send the response in JSON.
We are going to be creating two controllers, the first will be the Authentication Controller and the second is our Project Controller, we need the Authentication Controller in order to generate the token to use in Project Controller.

php artisan make:controller API/AuthController

This will create a folder called API in App/Http/Controllers. It will also create a new file called AuthController.php. Click on AuthController.php and update it with the following code.



<?php

namespace App\Http\Controllers\API;

use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Models\User;
use Illuminate\Support\Facades\Hash;



class AuthController extends Controller
{
    public function register(Request $request)
    {
        $validatedData = $request->validate([
            'name' => 'required|max:55',
            'email' => 'email|required|unique:users',
            'password' => 'required|confirmed'
        ]);

        $validatedData['password'] = Hash::make($request->password);

        $user = User::create($validatedData);

        $accessToken = $user->createToken('authToken')->accessToken;

        return response(['user' => $user, 'access_token' => $accessToken], 201);
    }

    public function login(Request $request)
    {
        $loginData = $request->validate([
            'email' => 'email|required',
            'password' => 'required'
        ]);

        if (!auth()->attempt($loginData)) {
            return response(['message' => 'This User does not exist, check your details'], 400);
        }

        $accessToken = auth()->user()->createToken('authToken')->accessToken;

        return response(['user' => auth()->user(), 'access_token' => $accessToken]);
    }
}


Enter fullscreen mode Exit fullscreen mode

In our AuthController, we created two methods: register and logic methods
In the register method, we use the Laravel Validate method to make sure that the name, email, and password is provided, this will also make sure that the email has not been taken and is a valid email address, the password must be confirmed before the user will be added.

After the validation, we use hash to encrypt the password before creating the user, we can't store plain password, lastly, we grab the access token and return it with the user’s information.
In the login method, we also validate the data been pass, to make sure the email and password are submitted, if the data did not correspond to any user, it will return a message that the user does not exist, if it corresponds, then it returns the user and the access token.

Let us create our ProjectController in the API using --api switch.

php artisan make:controller API/ProjectController --api --model=Project

The --api switch will create our Controller without the create and edit methods, those methods will present HTML templates.
Go to App/Http/Controller/API, and click on ProjectController, copy the code below and update the methods.



<?php

namespace App\Http\Controllers\API;

use App\Http\Controllers\Controller;
use App\Models\Project;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
use App\Http\Resources\ProjectResource;

class ProjectController extends Controller
{
    /**
     * Display a listing of the resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function index()
    {
        $projects = Project::all();
        return response([ 'projects' => ProjectResource::collection($projects), 'message' => 'Retrieved successfully'], 200);
    }

    /**
     * Store a newly created resource in storage.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    public function store(Request $request)
    {
        $data = $request->all();

        $validator = Validator::make($data, [
            'name' => 'required|max:255',
            'description' => 'required|max:255',
            'cost' => 'required'
        ]);

        if ($validator->fails()) {
            return response(['error' => $validator->errors(), 'Validation Error']);
        }

        $project = Project::create($data);

        return response(['project' => new ProjectResource($project), 'message' => 'Created successfully'], 201);
    }

    /**
     * Display the specified resource.
     *
     * @param  \App\Models\Project  $project
     * @return \Illuminate\Http\Response
     */
    public function show(Project $project)
    {
        return response(['project' => new ProjectResource($project), 'message' => 'Retrieved successfully'], 200);
    }

    /**
     * Update the specified resource in storage.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \App\Models\Project  $project
     * @return \Illuminate\Http\Response
     */
    public function update(Request $request, Project $project)
    {
        $project->update($request->all());

        return response(['project' => new ProjectResource($project), 'message' => 'Update successfully'], 200);
    }

    /**
     * Remove the specified resource from storage.
     *
     * @param  \App\Models\Project  $project
     * @return \Illuminate\Http\Response
     */
    public function destroy(Project $project)
    {
        $project->delete();

        return response(['message' => 'Deleted']);
    }
}


Enter fullscreen mode Exit fullscreen mode
  1. The index method will retrieve all the projects in the database with a success message (Retrieved successfully) and returns a status code of 200.
  2. The store method will validate and store a new project, just like the AuthController, and returns a status code of 201, also a message of "Created successfully".
  3. The show method will retrieve just one project that was passed through the implicit route model binding, and also returns an HTTP code of 200 if successful.
  4. The update method receives the HTTP request and the particular item that needs to be edited as a parameter. It updates the project and returns the appropriate response.
  5. The destroy method also receives a particular project through implicit route model binding and deletes it from the database.

Step 12: Create our Routes

Go to routes folder and click on api.php, and updates with the following code



<?php

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

/*
|--------------------------------------------------------------------------
| API Routes
|--------------------------------------------------------------------------
|
| Here is where you can register API routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| is assigned the "api" middleware group. Enjoy building your API!
|
*/

Route::middleware('auth:api')->get('/user', function (Request $request) {
    return $request->user();
});


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

Route::apiResource('projects', ProjectController::class)->middleware('auth:api');


Enter fullscreen mode Exit fullscreen mode

We added a register route and login routes which are post routes and also an apiResource route for projects utilizing the auth:api middleware.

Step 13: Testing

We are going to be testing our API with Postman, due to complexity and time, I might not be able to explain everything on Postman, but observe the red squares on this page
postman
register a user
register route
trying to get a user that does not exist
User not found
Login a registered user
Login successful
In other to access the projects routes, we must authenticate the user, in other to achieve this, we must add the user's token.
Copy the access token generated for the user when the user login in, click on Authorization on postman and select the type, Bearer Token, and paste the token by the input field by the right.
access token
Retrieve all the projects, but no project was created
empty database
Create a project
post a project
Retrieve all the projects
All projects
Retrieve just 1 project
One project
Update a project
Update
Delete a project
Delete

Finally, we have come to the end of the article, if you follow the article religiously, you will not make a mistake, but in case you can't follow all through, this is the link to the repo

You can encourage me by clicking the heart button to show love, you are can also leave a comment, suggestion, etc. You are also free to contact me through any of my contact details.

click the link to view my profile and follow me

Visit my other posts


Top comments (23)

Collapse
 
skdevelopers profile image
Mian Salman

You have not told us how to deal with access token because when I entered data in 127.0.0.1:8000/api/register and in 127.0.0.1:8000/api/projects route it shows login route not found and after adding this header X-Requested-With I am getting unauthorized message and on register link it's showing response to unauthorized access ?
how to deal with this I have 0 rows in user db ? show I make fake data first to generate access token

Collapse
 
benjaminv profile image
benjaminv • Edited

You should have modified the api routes not the web routes. I made such mistake at the beginning that resulted none of the url existed.
When I carefully corrected two typos in my scripts following the tutorial it worked.

Collapse
 
unreality23 profile image
Vaidotas

I agree, its confusing to say the least for beginner how to use postman in particular.

Collapse
 
nahid570 profile image
Nahid Faraji

I am having the same problem none of this url can be found.
surely, I am modified routes on api.php .

Collapse
 
skdevelopers profile image
Mian Salman

Now I figured out your first post is creating mess because you do not use token in this route 127.0.0.1:8000/api/projects this is wrong implementation as it is not using using token we make with passport auth.

Collapse
 
benjaminv profile image
benjaminv

Hi Mian, could you please give more details of this? It worked okay as I tried.

Collapse
 
vincenttetteh profile image
Vincent Tetteh • Edited

Target class [App\Http\Controllers\App\Http\Controllers\API\ProjectController] does not exist
please help me

Collapse
 
akomykel profile image
Michael Angelo Mahinay • Edited

Hi @vincenttetteh ! In case you're still having this issue, you may refer to this link:
stackoverflow.com/questions/638079...

We have the same problem and this solved mine

Collapse
 
unreality23 profile image
Vaidotas

In header please add Key: Accept Value: application/json

Collapse
 
skdevelopers profile image
Mian Salman

If composer dumpautoload is not helping then check if you have proper namespace declaration in ProjectController.php and double check for typos in class name/route declaration.

Collapse
 
ginsengsage profile image
GinsengSage

Hello, do you decide this problem?

Collapse
 
badadarr profile image
Badar Maulana

testingProjectLaravel % php artisan migrate

Illuminate\Database\QueryException

SQLSTATE[HY000] [1049] Unknown database 'testingprojectlaravel' (SQL: select * from information_schema.tables where table_schema = testingProjectLaravel and table_name = migrations and table_type = 'BASE TABLE')

at vendor/laravel/framework/src/Illuminate/Database/Connection.php:678
674▕ // If an exception occurs when attempting to run a query, we'll format the error
675▕ // message to include the bindings with SQL, which will make this exception a
676▕ // lot more helpful to the developer instead of just the database's errors.
677▕ catch (Exception $e) {
➜ 678▕ throw new QueryException(
679▕ $query, $this->prepareBindings($bindings), $e
680▕ );
681▕ }
682▕

  +33 vendor frames 
Enter fullscreen mode Exit fullscreen mode

34 artisan:37
Illuminate\Foundation\Console\Kernel::handle(Object(Symfony\Component\Console\Input\ArgvInput), Object(Symfony\Component\Console\Output\ConsoleOutput))

Collapse
 
skdevelopers profile image
Mian Salman

Your database is not set property or you have not any database with this name testing.......

Collapse
 
cocl_bz profile image
COCL オスマン

How to logout?

Collapse
 
cocl_bz profile image
COCL オスマン

I got it

public function logout (Request $request) {
$accessToken = auth()->user()->token();
$token= $request->user()->tokens->find($accessToken);
$token->revoke();

    return response(['message' => 'You have been successfully logged out.'], 200);
}
Enter fullscreen mode Exit fullscreen mode
Collapse
 
benjaminv profile image
benjaminv
public function logout (Request $request) {
        $accessToken = auth()->user()->token();
        $token = $request->user()->tokens->find($accessToken);
        $token->revoke();

        return response([
            'message' => 'You have been successfully logged out.',
        ], 200);
    }
Enter fullscreen mode Exit fullscreen mode

This makes sense I however got this error, do not know why,
Call to a member function token() on null

Thread Thread
 
benjaminv profile image
benjaminv

I figured out what happened. Before you can log a user out via API, you will need to pass authentication first and then log your login off. Therefore the route logout has to be underneath the middleware auth. For convenience I grouped it with the /user route that is used in this tutorial.

Route::middleware('auth:api')->group(function (){
    Route::get('/user', function (Request $request) {
        return $request->user();
    });
    Route::post('logout', [
        AuthController::class, 'logout'
    ]);
});
Enter fullscreen mode Exit fullscreen mode
Thread Thread
 
abhaydix07 profile image
Abhaydix07

Thankyou Sir

Collapse
 
cocl_bz profile image
COCL オスマン

request project id=1 GET api.test/api/projects/1
But, how to request by project name? can you give me a sample?
Thank you.

Collapse
 
dixonnixon profile image
andrey • Edited

after I`ve done all by hands I got an error. Please help)

Symfony\Component\Routing\Exception\RouteNotFoundException
Route [login] not defined.
192.168.20.105:8085/api/projects

Collapse
 
nahid570 profile image
Nahid Faraji

same here. Did you figure it out?

Collapse
 
simonhumanpixel profile image
simon-humanpixel

this works for me - its important to register a user first and get their token back and use it in future requests.

Collapse
 
naungyehtet profile image
Naung Ye Htet

can it be a security issue that exposing the id in resource?