Samiron

Laravel 7 RESTFul API with JSON Web Token (JWT)

We are going to learn how to delvelop Laravel RESRFul API with JSON Web Token (JWT).

1. Create Laravel porject

To install new laravel project run the command

laravel new laravel-rest-api-jwt-auth

To go to the project directory run the command

cd laravel-rest-api-jwt-auth

Open the project with your favorite code editor and put the credentials of database in .env file

DB_DATABASE=laravel_rest_api_jwt_auth
DB_USERNAME=root
DB_PASSWORD=

2. Install jwt-auth package

See the documentation here

To install the package run the command

composer require tymon/jwt-auth:dev-develop --prefer-source

To publish the package configurations run the command

php artisan vendor:publish --provider="Tymon\JWTAuth\Providers\LaravelServiceProvider"

To generating JWT authentication keys run the command

php artisan jwt:secret

Now we will registering JWT middleware. Open app/Http/Kernel.php and add this line

protected $routeMiddleware = [
    ...
    'auth.jwt' => \Tymon\JWTAuth\Http\Middleware\Authenticate::class
];

3. Create routes

Now we will create our routes in routes/api.php

Route::post("login", "AuthController@login");
Route::post("register", "AuthController@register");

Route::group(["middleware" => "auth.jwt"], function () {
    Route::get("logout", "AuthController@logout");
    Route::resource("tasks", "TaskController");
});

4. Update User model

Open app/User.php file and add bolded lines

namespace App;

use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Tymon\JWTAuth\Contracts\JWTSubject;

class User extends Authenticatable implements JWTSubject
{
    use Notifiable;

And add these methods

public function getJWTIdentifier()
{
    return $this->getKey();
}

public function getJWTCustomClaims()
{
    return [];
}

5. Create AuthController

Now we are going to creating AuthController for Login and Registration, run the command

php artisan make:controller AuthController

Open app\Http\Controllers\AuthController.php file and add this property

public $loginAfterSignUp = true;

Now add login method

public function login(Request $request)
{
    $credentials = $request->only("email", "password");
    $token = null;

    if (!$token = JWTAuth::attempt($credentials)) {
        return response()->json([
            "status" => false,
            "message" => "Unauthorized"
        ]);
    }

    return response()->json([
        "status" => true,
        "token" => $token
    ]);
}

Here is our register method

public function register(Request $request)
{
    $this->validate($request, [
        "name" => "required|string",
        "email" => "required|email|unique:users",
        "password" => "required|string|min:6|max:10"
    ]);

    $user = new User();
    $user->name = $request->name;
    $user->email = $request->email;
    $user->password = bcrypt($request->password);
    $user->save();

    if ($this->loginAfterSignUp) {
        return $this->login($request);
    }

    return response()->json([
        "status" => true,
        "user" => $user
    ]);
}

And this is our last and final logout method

public function logout(Request $request)
{
    $this->validate($request, [
        "token" => "required"
    ]);

    try {
        JWTAuth::invalidate($request->token);

        return response()->json([
            "status" => true,
            "message" => "User logged out successfully"
        ]);
    } catch (JWTException $exception) {
        return response()->json([
            "status" => false,
            "message" => "Ops, the user can not be logged out"
        ]);
    }
}

Finally we need to use this Classes at the top

use JWTAuth;
use App\User;
use Tymon\JWTAuth\Exceptions\JWTException;

Here is our completed AuthController

namespace App\Http\Controllers;

use JWTAuth;
use App\User;
use Illuminate\Http\Request;
use Tymon\JWTAuth\Exceptions\JWTException;

class AuthController extends Controller
{
    public $loginAfterSignUp = true;

    public function login(Request $request)
    {
        $credentials = $request->only("email", "password");
        $token = null;

        if (!$token = JWTAuth::attempt($credentials)) {
            return response()->json([
                "status" => false,
                "message" => "Unauthorized"
            ]);
        }

        return response()->json([
            "status" => true,
            "token" => $token
        ]);
    }

    public function register(Request $request)
    {
        $this->validate($request, [
            "name" => "required|string",
            "email" => "required|email|unique:users",
            "password" => "required|string|min:6|max:10"
        ]);

        $user = new User();
        $user->name = $request->name;
        $user->email = $request->email;
        $user->password = bcrypt($request->password);
        $user->save();

        if ($this->loginAfterSignUp) {
            return $this->login($request);
        }

        return response()->json([
            "status" => true,
            "user" => $user
        ]);
    }

    public function logout(Request $request)
    {
        $this->validate($request, [
            "token" => "required"
        ]);

        try {
            JWTAuth::invalidate($request->token);

            return response()->json([
                "status" => true,
                "message" => "User logged out successfully"
            ]);
        } catch (JWTException $exception) {
            return response()->json([
                "status" => false,
                "message" => "Ops, the user can not be logged out"
            ]);
        }
    }
}

6. Create Task Model, Migration and Controller

To create Task model, migration and controller run the command

php artisan make:model Task -mcr

Open task migration file form database\migrations and add this schema to up method

Schema::create('tasks', function (Blueprint $table) {
    $table->id();
    $table->string('title');
    $table->string('details');
    $table->unsignedBigInteger('created_by');
    $table->timestamps();

    $table->foreign('created_by')->references('id')->on('users')->onDelete('cascade');
});

Open app/User.php file and add this method at end

public function tasks()
{
    return $this->hasMany(Task::class, 'created_by', 'id');
}

Here is our completed User model

namespace App;

use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Tymon\JWTAuth\Contracts\JWTSubject;

class User extends Authenticatable implements JWTSubject
{
    use Notifiable;

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

    /**
     * The attributes that should be hidden for arrays.
     *
     * @var array
     */
    protected $hidden = [
        'password', 'remember_token',
    ];

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

    public function getJWTIdentifier()
    {
        return $this->getKey();
    }

    public function getJWTCustomClaims()
    {
        return [];
    }

    public function tasks()
    {
        return $this->hasMany(Task::class, 'created_by', 'id');
    }
}

7. Migrate database and insert dummy data

To migrate run the command

php artisan migrate

To insert dummy data follow the steps bellow

Open database/factories/UserFactory.php

Replace password with 'password' => bcrypt('123456'),

$factory->define(Task::class, function (Faker $faker) {
    return [
        'title' => $faker->sentence,
        'details' => $faker->sentence,
        'created_by' => rand(1, 10)
    ];
});

use App\Task; at top

Here is our completed UserFactory.php

/** @var \Illuminate\Database\Eloquent\Factory $factory */

use App\User;
use App\Task;
use Faker\Generator as Faker;
use Illuminate\Support\Str;

/*
|--------------------------------------------------------------------------
| Model Factories
|--------------------------------------------------------------------------
|
| This directory should contain each of the model factory definitions for
| your application. Factories provide a convenient way to generate new
| model instances for testing / seeding your application's database.
|
*/

$factory->define(User::class, function (Faker $faker) {
    return [
        'name' => $faker->name,
        'email' => $faker->unique()->safeEmail,
        'email_verified_at' => now(),
        'password' => bcrypt("123456"),
        'remember_token' => Str::random(10),
    ];
});

$factory->define(Task::class, function (Faker $faker) {
    return [
        'title' => $faker->sentence,
        'details' => $faker->sentence,
        'created_by' => rand(1, 10)
    ];
});

Now run these commands to insert some dummy data

php artisan tinker
factory(App\User::class, 10)->create()
factory(App\Task::class, 50)->create()

8. Task CRUD functionality

Open app\Http\Controllers\TaskController.php

add user property and construct method

protected $user;

public function __construct()
{
    $this->user = JWTAuth::parseToken()->authenticate();
}

Add index method to get the task list those are created by the logged in user

public function index()
{
    $tasks = $this->user->tasks()->get(["id", "title", "details", "created_by"])->toArray();

    return $tasks;
}

Here is our store method to store tast data with the logged in user id

public function store(Request $request)
{
    $this->validate($request, [
        "title" => "required",
        "details" => "required"
    ]);

    $task = new Task();
    $task->title = $request->title;
    $task->details = $request->details;

    if ($this->user->tasks()->save($task)) {
        return response()->json([
            "status" => true,
            "task" => $task
        ]);
    } else {
        return response()->json([
            "status" => false,
            "message" => "Ops, task could not be saved."
        ], 500);
    }
}

This is update method to update task data with logged in user id

public function update(Request $request, Task $task)
{
    $this->validate($request, [
        "title" => "required",
        "details" => "required"
    ]);

    $task->title = $request->title;
    $task->details = $request->details;

    if ($this->user->tasks()->save($task)) {
        return response()->json([
            "status" => true,
            "task" => $task
        ]);
    } else {
        return response()->json([
            "status" => false,
            "message" => "Ops, task could not be updated."
        ], 500);
    }
}

And final delete method to delete task data

public function destroy(Task $task)
{
    if ($task->delete()) {
        return response()->json([
            "status" => true,
            "task" => $task
        ]);
    } else {
        return response()->json([
            "status" => false,
            "message" => "Ops, task could not be deleted."
        ]);
    }
}

This is our completed TaskController

namespace App\Http\Controllers;

use JWTAuth;
use App\Task;
use Illuminate\Http\Request;

class TaskController extends Controller
{
    protected $user;

    public function __construct()
    {
        $this->user = JWTAuth::parseToken()->authenticate();
    }

    /**
     * Display a listing of the resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function index()
    {
        $tasks = $this->user->tasks()->get(["id", "title", "details", "created_by"])->toArray();

        return $tasks;
    }

    /**
     * Show the form for creating a new resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function create()
    {
        //
    }

    /**
     * Store a newly created resource in storage.
     *
     * @param  \Illuminate\Http\Request $request
     * @return \Illuminate\Http\Response
     */
    public function store(Request $request)
    {
        $this->validate($request, [
            "title" => "required",
            "details" => "required"
        ]);

        $task = new Task();
        $task->title = $request->title;
        $task->details = $request->details;

        if ($this->user->tasks()->save($task)) {
            return response()->json([
                "status" => true,
                "task" => $task
            ]);
        } else {
            return response()->json([
                "status" => false,
                "message" => "Ops, task could not be saved."
            ], 500);
        }
    }

    /**
     * Display the specified resource.
     *
     * @param  \App\Task $task
     * @return \Illuminate\Http\Response
     */
    public function show(Task $task)
    {
        //
    }

    /**
     * Show the form for editing the specified resource.
     *
     * @param  \App\Task $task
     * @return \Illuminate\Http\Response
     */
    public function edit(Task $task)
    {
        //
    }

    /**
     * Update the specified resource in storage.
     *
     * @param  \Illuminate\Http\Request $request
     * @param  \App\Task $task
     * @return \Illuminate\Http\Response
     */
    public function update(Request $request, Task $task)
    {
        $this->validate($request, [
            "title" => "required",
            "details" => "required"
        ]);

        $task->title = $request->title;
        $task->details = $request->details;

        if ($this->user->tasks()->save($task)) {
            return response()->json([
                "status" => true,
                "task" => $task
            ]);
        } else {
            return response()->json([
                "status" => false,
                "message" => "Ops, task could not be updated."
            ], 500);
        }
    }

    /**
     * Remove the specified resource from storage.
     *
     * @param  \App\Task $task
     * @return \Illuminate\Http\Response
     */
    public function destroy(Task $task)
    {
        if ($task->delete()) {
            return response()->json([
                "status" => true,
                "task" => $task
            ]);
        } else {
            return response()->json([
                "status" => false,
                "message" => "Ops, task could not be deleted."
            ]);
        }
    }
}

9. Test the project

To test the project please watch the video. This will help you in better way.

Laravel RESTFul API with JSON Web Token (JWT)

10. Source File

To get completed project with source file visit this github link

https://github.com/samironbarai/laravel-rest-api-jwt-auth