[Tutorial] Laravel uses JWT-certified Restful API

Keywords: PHP Laravel JSON Database

Articles are forwarded from the professional Laravel developer community with original links: https://learnku.com/laravel/t...

In this article, we will learn how to use JWT authentication to build restful API s in Laravel. JWT stands for JSON Web Tokens. We will also use API to create full-featured CRUD applications for user products.

API is a very good choice when using cross-platform applications. In addition to the website, your product may also have Android and iOS applications. In this case, the API is equally excellent, because you can write different front-ends without changing any back-end code. When using API, the server will return some data in JSON(JavaScript Object Notation) format by clicking GET, POST or other types of requests with some parameters, which will be processed by the client application.

Explain

Let's first write down the details and functions of our application. We will use JWT authentication to build a list of basic user products in laravel using the restful API.

A. User will use the following functions

  • Register and create a new account
  • Log in to their account
  • Log out and discard token and leave the application
  • Get the details of the logged-in user
  • Retrieving the list of products available to users
  • Find specific products by ID
  • Add new products to the user product list
  • Editing Existing Product Details
  • Delete existing products from the user list

A. User

  • name
  • email
  • password

A Product must be filled in

  • name
  • price
  • quantity

Create new projects

By running the following command, we can start and create a new Laravel project.

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

This creates a new Laravel project in a directory called jwt.

Configuring JWT extension packages

We will use it. tymondesigns/jwt-auth Extension packages to let us use JWT in Laravel.

Install the tymon/jwt-auth extension package

Let's install this extension package in this Laravel application. If you are using Laravel 5.5 or more, run the following command to get the dev-develop ment version of the JWT package:

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

If you are using Laravel 5.4 or later, run the following command:

composer require tymon/jwt-auth

For applications with Laravel version less than 5.5, you also need to set up service providers and aliases in the config/app.php file.

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

If your version of Laravel is 5.5 or more, Laravel will automatically discover packages.

Publish configuration files

For 5.5 or more versions of Lavel, use the following command to publish the configuration file:

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

For previous versions of Lavel, you should run the following command:

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

The above command generates the config/jwt.php configuration file. Removing the comment section, the configuration file will look like this:

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

Generating JWT keys

The JWT token is issued with an encrypted key. For Laravel 5.5 or above, run the following command to generate a key for issuing a token.

php artisan jwt:secret

Laravel version runs below 5.5:

php artisan jwt:generate

This tutorial uses Laravel 5.6. The next steps in the tutorial were tested only in 5.5 and 5.6. It may not be applicable to Laravel 5.4 or below. You can read Documentation for older versions of Laravel.

Registration Middleware

The JWT Authentication Extension Package comes with middleware that allows us to use. Register auth.jwt Middleware in app/Http/Kernel.php:

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

This middleware verifies the user's authentication by checking the token attached to the request. If the user is not authenticated, the middleware throws an Unauthorized HttpException exception.

Route setting

Before we start, we will set up routing for all the points discussed in this tutorial. Open routes/api.php and copy the following routing 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 needs to implement the Tymon JWTAuthContracts JWT Subject interface in the User model. This interface needs to implement two methods: getJWTIdentifier and getJWTCustomClaims. Update app/User.php with the following content.

<?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 Restful API logic in laravel using JWT authentication.

Users need name, email and password to register. So let's create a form request to validate the data. Create a form request called RegisterAuthRequest by running the following command:

php artisan make:request RegisterAuthRequest

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

<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

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

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

Run the following command to create a new ApiController:

php artisan make:controller ApiController

This will create the ApiController.php file in the app/Http/Controllers directory. Paste the following code 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 happened to the above code.

In the register method, we receive RegisterAuthRequest. Create users using the data in the request. If the loginAfterSignUp attribute is true, the login method is used to login the user after registration. Otherwise, the successful response will be returned with the user data.

In the login method, we get a subset of requests that contain only e-mail and passwords. Call JWTAuth::attempt() with the input value as a parameter, and the response is stored in a variable. If false is returned from the attempt method, a failure response is returned. Otherwise, a successful response will be returned.

In the logout method, verify whether the request contains token validation. Invalidate the token by calling the invalidate method and return a successful response. If a JWTException exception is caught, a failed response is returned.

In the getAuthUser method, verify that the request contains a token field. Then call the authenticate method, which returns the authenticated user. Finally, the response with the user is returned.

The authentication part is now complete.

Build the product part

To create the product part, we need product models, controllers, and migration files. Run the following commands to create the Product model, controller and migration files.

php artisan make:model Product -mc

It creates a new database migration file create_products_table.php under the database/migrations directory and changes 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 the fillable attribute to the Product model. Open the Product.php file in the app directory and add attributes.

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

Now set the database credentials in the. env file and migrate the database by running the following commands.

php artisan migrate

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

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

Open the ProductController.php file in the app/Http/Controllers directory. Add the use instruction at the beginning of the file to override one.

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

Now we will implement five approaches.

  • index to obtain a list of all products for authenticated users
  • show, get specific products based on ID
  • Store, store new products in the product list
  • Update, update product details according to ID
  • destroy, delete products from the list according to ID

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

protected $user;

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

parseToken parses the token from the request, and authenticate authenticates the user through the token.

Let's add the index method.

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

The above code is very simple, we just use Eloquent method to get all the products, and then compose the results into an array. Finally, we return to this array. Laravel will automatically convert it to JSON and create a response code that is successful for 2000.

Continue 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 is also very easy to understand. We just need to find the product by ID. If the product does not exist, 400 fault response is returned. Otherwise, the product array will be returned.

Next comes 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, verify that the request contains the name, price and quantity. Then, use the data in the request to create a new product model. If the product is successfully written to the database, a successful response will be returned, otherwise a custom 500 failure response will be returned.

Implementation of 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 get the product through id. If the product does not exist, a 400 response is returned. Then, we fill the data in the request into the product details using the fill method. Update the product model and save it to the database. If the record is updated successfully, return a 200 successful response, otherwise return 500 internal server error response 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 get the product based on ID and return 400 responses if the product does not exist. Then we delete the product and return the appropriate response based on the successful status of the deletion operation.

The controller code is now complete. Complete controller code Here.

test

Let's first test identity authentication. We will use the serve r command to start the Web service on the developer. You can also use the virtual host instead. Run the following command to start the Web service.

php artisan serve

It will listen on localhost:8000

To test restful API's, we use Postman . After filling in the request body, we request the register routing.

Send the request and you will get the token.

Our users are now registered and authenticated. We can send another request to detect the login routing, which returns 200 and token.

Getting User Details

The test identity authentication has been completed. Next, test the product section, and first create a product.

Now, get the product by requesting the index method.


You can test other routes, and they will all work.

Tutorial source code GitHub.

Posted by Noctule on Thu, 19 Sep 2019 01:21:22 -0700