Restful API In Laravel 5.6 Using jwt Authentication

66,794

In this article, we will learn how to build restful API in Laravel using JWT Authentication. JWT stands for JSON Web Tokens. We will also create a fully functional CRUD for user products using API.

API’s are great when working with cross-platform applications. In addition to a website, your product may have an android and iOS app. In such cases, API’s are great because you can write different front ends without changing any backend code. When working with API, you simply hit a GET, POST or different kind of request with some arguments and server returns some data in JSON (JavaScript Object Notation) format which is handled by the client-side application.

Details

Let’s first write down our application details and features. We will build a basic user products list with restful API in laravel using JWT authentication.

A User will be able to

  • sign up and create a new account
  • log-in to their account
  • log out to discard the token and leave the application
  • get the details of the logged in user
  • retrieve a list of products available for the user
  • find a specific product by id
  • add a new product to the user product list
  • edit an existing product details
  • delete an existing product from the user list

A User requires

  • name
  • email
  • password

A Product requires

  • name
  • price
  • quantity

Creating a new project

Let’s start by creating a new laravel project by running the following command.

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

It will create a new laravel project in the folder named jwt.

Configuring JWT Package

We will be using tymondesigns/jwt-auth package for working with JWT in Laravel.

Installing tymon/jwt-auth package

Let’s install this package in our Laravel application. If you are using Laravel version 5.5 or above, run the following command to require a dev-develop version of jwt package.

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

If you are using the laravel version 5.4 or less, run the following command

composer require tymon/jwt-auth

For versions less than 5.5, you are also required to set service provider and alias in config/app.php file.

'providers' => [
    ....
    Tymon\JWTAuth\Providers\JWTAuthServiceProvider::class,
    ....
],
'aliases' => [
    ....
    'JWTAuth' => Tymon\JWTAuth\Facades\JWTAuth::class,
    'JWTFactory' => 'Tymon\JWTAuth\Facades\JWTFactory',
    ....
],

If you are using version 5.5 or above, laravel does it automatically using Package Auto-Discovery.

Publishing Configuration File

Publish the configuration file using the following command for versions 5.5 or above

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

If you are using previous versions of laravel, run the following command to publish the configuration

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

Above command will generate a config/jwt.php configuration file. Here’s the configuration file without any comments.

<?php

return [

    'secret' => env('JWT_SECRET'),

    'keys' => [

        'public' => env('JWT_PUBLIC_KEY'),

        'private' => env('JWT_PRIVATE_KEY'),

        'passphrase' => env('JWT_PASSPHRASE'),
    ],

    'ttl' => env('JWT_TTL', 60),

    'refresh_ttl' => env('JWT_REFRESH_TTL', 20160),

    'algo' => env('JWT_ALGO', 'HS256'),

    'required_claims' => [
        'iss',
        'iat',
        'exp',
        'nbf',
        'sub',
        'jti',
    ],

    'persistent_claims' => [
        // 'foo',
        // 'bar',
    ],

    'lock_subject' => true,

    'leeway' => env('JWT_LEEWAY', 0),

    'blacklist_enabled' => env('JWT_BLACKLIST_ENABLED', true),

    'blacklist_grace_period' => env('JWT_BLACKLIST_GRACE_PERIOD', 0),

    'decrypt_cookies' => false,

    'providers' => [
        'jwt' => Tymon\JWTAuth\Providers\JWT\Lcobucci::class,

        'auth' => Tymon\JWTAuth\Providers\Auth\Illuminate::class,

        'storage' => Tymon\JWTAuth\Providers\Storage\Illuminate::class,
    ],
];

Generate JWT Key

JWT tokens will be signed with an encryption key. Run the following command for laravel 5.5 or above to generate the secret key used to sign the tokens.

php artisan jwt:secret

For versions below 5.5

php artisan jwt:generate

This tutorial uses laravel 5.6. Next steps of the tutorial are only tested for laravel 5.5 and 5.6. Tutorial steps below may not work for laravel 5.4 or less. You may consider the documentation for laravel previous versions.

Registering Middleware

JWT auth package comes up with middlewares that we can use. Register auth.jwt middleware in app/Http/Kernel.php

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

This middleware verifies that the user is authenticated by checking the token sent with the request. If the user is not authenticated, the middleware will throw UnauthorizedHttpException exception.

Set up Routes

We will set up routes for all the endpoints that we discussed at the start of the tutorial. Open up routes/api.php and copy the routes below to your file.

Route::post('login', 'ApiController@login');
Route::post('register', 'ApiController@register');

Route::group(['middleware' => 'auth.jwt'], function () {
    Route::get('logout', 'ApiController@logout');

    Route::get('user', 'ApiController@getAuthUser');

    Route::get('products', 'ProductController@index');
    Route::get('products/{id}', 'ProductController@show');
    Route::post('products', 'ProductController@store');
    Route::put('products/{id}', 'ProductController@update');
    Route::delete('products/{id}', 'ProductController@destroy');
});

Update User Model

JWT requires implementing the Tymon\JWTAuth\Contracts\JWTSubject interface on the User model. This interface requires implementing two methods getJWTIdentifier and getJWTCustomClaims. Update app/User.php file with the contents present below.

<?php

namespace App;

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',
    ];

    /**
     * Get the identifier that will be stored in the subject claim of the JWT.
     *
     * @return mixed
     */
    public function getJWTIdentifier()
    {
        return $this->getKey();
    }

    /**
     * Return a key value array, containing any custom claims to be added to the JWT.
     *
     * @return array
     */
    public function getJWTCustomClaims()
    {
        return [];
    }
}

JWT Authentication Logic

Let’s write the logic for restful API in laravel using JWT authentication.

User registration requires name, email, and password. So, let’s create a form request to validate the data. Create a Form request named RegisterAuthRequest by running the following command:

php artisan make:request RegisterAuthRequest

It will create RegisterAuthRequest.php file in app/Http/Requests directory. Paste the code below into the file.

<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class RegisterAuthRequest extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     *
     * @return bool
     */
    public function authorize()
    {
        return true;
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules()
    {
        return [
            'name' => 'required|string',
            'email' => 'required|email|unique:users',
            'password' => 'required|string|min:6|max:10'
        ];
    }
}

Create a new ApiController by running the command:

php artisan make:controller ApiController

This will create the ApiController.php file in app/Http/Controllers directory. Paste the code below into the file.

<?php

namespace App\Http\Controllers;

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

class ApiController extends Controller
{
    public $loginAfterSignUp = true;

    public function register(RegisterAuthRequest $request)
    {
        $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([
            'success' => true,
            'data' => $user
        ], 200);
    }

    public function login(Request $request)
    {
        $input = $request->only('email', 'password');
        $jwt_token = null;

        if (!$jwt_token = JWTAuth::attempt($input)) {
            return response()->json([
                'success' => false,
                'message' => 'Invalid Email or Password',
            ], 401);
        }

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

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

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

            return response()->json([
                'success' => true,
                'message' => 'User logged out successfully'
            ]);
        } catch (JWTException $exception) {
            return response()->json([
                'success' => false,
                'message' => 'Sorry, the user cannot be logged out'
            ], 500);
        }
    }

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

        $user = JWTAuth::authenticate($request->token);

        return response()->json(['user' => $user]);
    }
}

Let me explain what’s happening in the code above.

In the register method, we accept RegisterAuthRequest. A user is created with the data present in the request. If the loginAfterSignUp property is true, it will log-in the user by calling the login method after registering. Otherwise, a successful response is returned with the user data.

In the login method, we get a subset of the request only containing email and password. JWTAuth::attempt() is called with input as the argument and the response is saved in a variable. If false is returned from the attempt method, we return a failure response. Otherwise, a success response is returned.

In the logout method, the request is validated that it contains the token field. The token is invalidated by calling the invalidate method and a successful response is returned. If the JWTException exception caught, a failure response is returned.

In the getAuthUser method, the request is validated that it contains the token field. Then the authenticate method is called which returns the authenticated user. Finally, the response with the user is returned.

Authentication part is now complete.

Building the Product Part

To create the product part, we will need a Product model, controller, and migration. Run the following command to create the Product model, controller, and migration.

php artisan make:model Product -mc

It will create a new database migration file create_products_table.php in database/migrations directory. Update the up method.

public function up()
{
    Schema::create('products', function (Blueprint $table) {
        $table->increments('id');
        $table->integer('user_id');
        $table->string('name');
        $table->integer('price');
        $table->integer('quantity');
        $table->timestamps();

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

Add a fillable property to the Product model. Open the Product.php file in the app directory and add the property.

protected $fillable = [
    'name', 'price', 'quantity'
];

Now set up your database credentials in the .env file and migrate the database by running the following command.

php artisan migrate

Now, we have to add a relationship in the User model to retrieve the related products. In app/User.php add the following method.

public function products()
{
    return $this->hasMany(Product::class);
}

Open the ProductController.php file in app/Http/Controllers directory. Add the following use directives at the beginning of the file overwriting the previous one.

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

Now we will implement five methods.

  • index, to get a list of all the products for the authenticated user
  • show, to get a specific product by its id
  • store, to store a new product to the list of products
  • update, to update product details by its id
  • destroy, to delete a product from the list by its id

Add a constructor to get the authenticated user and save it in the user property.

protected $user;

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

parseToken will parse the token from the request and authenticate method will authenticate the user via the token.

Let’s add the index method.

public function index()
{
    return $this->user
        ->products()
        ->get(['name', 'price', 'quantity'])
        ->toArray();
}

Above code is very easy. We are simply using eloquent to get all of the user products and then we convert the results into an array. Finally, we return it. Laravel will automatically convert it into JSON and create a 200 success response code.

Moving on to implement the show method.

public function show($id)
{
    $product = $this->user->products()->find($id);

    if (!$product) {
        return response()->json([
            'success' => false,
            'message' => 'Sorry, product with id ' . $id . ' cannot be found'
        ], 400);
    }

    return $product;
}

This one is also easy to understand. We simply find the product with the id. If the product is not present, a failure 400 response is returned. Otherwise, the product is returned.

Next method is the store method.

public function store(Request $request)
{
    $this->validate($request, [
        'name' => 'required',
        'price' => 'required|integer',
        'quantity' => 'required|integer'
    ]);

    $product = new Product();
    $product->name = $request->name;
    $product->price = $request->price;
    $product->quantity = $request->quantity;

    if ($this->user->products()->save($product))
        return response()->json([
            'success' => true,
            'product' => $product
        ]);
    else
        return response()->json([
            'success' => false,
            'message' => 'Sorry, product could not be added'
        ], 500);
}

In the store method, the request is validated that it contains name, price, and quantity. Then, a new Product model is created with the data present in the request. If the product is saved successfully in the database, a success response is returned. Otherwise, a failure 500 custom response is returned.

Let’s implement the update method.

public function update(Request $request, $id)
{
    $product = $this->user->products()->find($id);

    if (!$product) {
        return response()->json([
            'success' => false,
            'message' => 'Sorry, product with id ' . $id . ' cannot be found'
        ], 400);
    }

    $updated = $product->fill($request->all())
        ->save();

    if ($updated) {
        return response()->json([
            'success' => true
        ]);
    } else {
        return response()->json([
            'success' => false,
            'message' => 'Sorry, product could not be updated'
        ], 500);
    }
}

In the update method, we find the product with the id. If the product is not present, a 400 response is returned. Then we update the product details with the details present in the request by using the fill method. Updated product model is then saved in the database. If the record updates successfully, a 200 success response is returned. Otherwise, a 500 internal server error response is returned to the client.

Now, let’s implement the destroy method.

public function destroy($id)
{
    $product = $this->user->products()->find($id);

    if (!$product) {
        return response()->json([
            'success' => false,
            'message' => 'Sorry, product with id ' . $id . ' cannot be found'
        ], 400);
    }

    if ($product->delete()) {
        return response()->json([
            'success' => true
        ]);
    } else {
        return response()->json([
            'success' => false,
            'message' => 'Product could not be deleted'
        ], 500);
    }
}

In the destroy method, we find the product with its id. If the product is not present, a 400 response is returned. Then we delete the product and return an appropriate response based on the success of the delete operation.

The controller code is now complete. Here is the full controller code.

Testing

Let’s first test the authentication process. We will be using serve command to serve the application on the development server. You can use virtual host if you want. Run the following command to serve the application.

php artisan serve

It will start a development server at localhost:8000

For testing restful API’s, we will use Postman. Let’s make a call to the register route after filling the request body.

laravel postman register

Send the request and you will get a token back.

laravel api token

Our user is now registered and authenticated. We can check the login route by sending another request. It will send a 200 response with the token.

laravel api login

Let’s retrieve the user details.

laravel api user

Testing authentication is complete. Let’s test the product part. First store the product.

laravel api products

Now, let’s retrieve the product by requesting the index method.

laravel api products

You can test other routes and they will work.

Full Source Code for this tutorial is available at .

You might also like
Comments