Laravel REST API with JWT Authentication for Login and Role-based permissions
Tags: laravel, jwt, authentication
Published: 2023-02-08 14:13:18

### **Table of contents**
* [Introduction](#introduction)
* [Create basic User REST API in Laravel](#restapi)
* [Introduce JWT-Auth Library](#jwt-intro)
* [Using JWT-Auth Library](#jwt-use)
* [Login Demostration in Postman and React-app](#test)

### Introduction <a name="introduction"></a>
This article describes how to make a simple user REST API with Laravel which will make use of JWT (JSON Web Token) technology for authentication. This will allow users login and obtain access to private pages in a certain web app.



### Create basic REST API in Laravel<a name="restapi"></a>.

- **Create Laravel Project:** 
Use the following command in the console to create a Laravel project:

`composer create-project --prefer-dist laravel/laravel projectname`

- **Default User Model and Migration:**
The User model and User migration classes are already present in the newly created Laravel project.

- **Configure the Database:**
Open the .env file in the project root and update the database configuration by filling in the variables with your database information.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/j6eaocfc9fxczgmanjsx.JPG)

#### Modifying the User Model for Database Schema

To allow the user to log in with their *username* and *password*, we need to save this information in the database. To do this, modify the code in the User Model class as follows:

```php
protected $fillable = [
        'username',
        'password',
    ];
```


- To create a *Role* Model class and represent the roles that each user would have, run the following command in the console: `php artisan make:model Role`. 
Next, edit the class and add the following code to establish a One-to-Many relationship between the User and Role models:

```php
class Role extends Model
{
    use HasFactory;

    protected $fillable = [
        'privilege',
        'ref_id',
        'user_id',
    ];

    public function user() {
        return $this->belongsTo('App\Models\User');
    }
}
```

- Create a Migration for the new *Role* Model : `php artisan make:migration create_roles_table`

- Modify *User* Model class by adding a method which returns the roles asociated to the User itself:

```php
//Outside class scope
use DB;

public function getRoleIDs() {
        $roleIDs = DB::table('roles')->where('user_id',$this->id)->pluck('ref_id');
        return $roleIDs;
    }
```

- Modify the up function in *User* and *Role* Migration classes to configure the schema of the database:

**User Migration**

```php
public function up()
    {
        Schema::create('users', function (Blueprint $table) {
            $table->id();
            $table->string('username')->unique();                
            $table->string('password');
            $table->timestamps();
        });
    }
```

**Role Migration**

```php
public function up()
    {
        Schema::create('roles', function (Blueprint $table) {
            $table->id();
            $table->string('privilege');
            $table->integer('ref_id')->unsigned();
            $table->bigInteger('user_id')->unsigned();
            $table->timestamps();
        });
    }
```

### Introduce JWT-Auth Library <a name="jwt-intro"></a>



- To install the JWT-Auth library, use the following command in the console: `composer require tymon/jwt-auth`

- To add the library as a service provider, open the app.php file in the config folder and add the following key to the providers array: `Tymon\JWTAuth\Providers\LaravelServiceProvider::class`

- Run the following command to publish the configuration : 
`php artisan vendor:publish 
--provider="Tymon\JWTAuth\Providers\LaravelServiceProvider"`

#### Configure JWT-Auth

- Generate a secret key that will be used to sign our tokens,  by running the following command: `php artisan jwt:secret`

- Open *auth.php* file located in *config* folder

- Modify the *defaults* array to look like this:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/93l81r2ccx00ov9mwfni.JPG)

- Modify the *guards* array to looks like this:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/or2vyyv73weo01omk7db.JPG)

### Using JWT-Auth Library <a name="jwt-use"></a>

#### Configure User Model

- Import JWTSubject in *User* Model class and make this class implement JWTSubject:

```php
use Tymon\JWTAuth\Contracts\JWTSubject;

class User extends Authenticatable implements JWTSubject
```

- Add these two methods: 

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

To obtain the identifier that will be stored in the JWT.

```php
public function getJWTCustomClaims() {
        return [];
    }  
```

This method returns a key value array, containing any custom claims to be added to the JWT.

- Migrate the DataBase schemas with this command: `php artisan migrate`

#### API Set-up

- Make a *AuthController* Controller class with the command: `php artisan make:controller AuthController`

- Here is a simple base code that can be put into the *AuthController* class to have login and register API endpoints:

```php
namespace App\Http\Controllers;

use Illuminate\Support\Facades\Auth;
use App\Http\Controllers\Controller;

class AuthController extends Controller
{
    /**
     * Create a new AuthController instance.
     *
     * @return void
     */
    public function __construct()
    {
        $this->middleware('auth:api', ['except' => ['login','register']]);
    }


/**
     * Get a JWT via given credentials.
     *
     * @return \Illuminate\Http\JsonResponse
     */
    public function login()
    {
        $credentials = request(['username', 'password']);

        if (!auth()->attempt($credentials)) {
            return response()->json(['errors' => 'Invalid username and Password'], 401);
        }

        $token = auth('api')->claims(['roles' => auth('api')->user()->getRoleIDs()])->attempt($credentials);

        return $this->respondWithToken($token);
    }


    public function register(Request $request)
    {
        $validator = Validator::make($request->all(), [
            'username' => 'required',
            'password' => 'required|string|min:4',
        ]);
        
        if($validator->fails()){
            return response()->json($validator->errors()->toJson(),400);
        }

        $user = User::create(array_merge(
            $validator->validate(),
            ['password' => bcrypt($request->password),]
        ));

        $role = Role::create([
            'privilege' => 'user',
            'ref_id' => 2001,
            'user_id' => $user->id,
        ]);

        return response()->json([
            'message' => '¡Successfully registered user!',
            'user' => $user,
            'privilege' => $role->privilege,
        ], 201);
    }


/**
     * Get the token array structure.
     *
     * @param  string $token
     *
     * @return \Illuminate\Http\JsonResponse
     */
    protected function respondWithToken($token)
    {
        return response()->json([
            'access_token' => $token,
            'token_type' => 'bearer',
            'expires_in' => auth()->factory()->getTTL() * 60
        ]);
    }
}
```
Note that in the login method, when the login is valid, it puts the roles associated with the user into custom claims within the token.

- Configure API Routes:

Open the *api.php* file in *routes* folder and add the login API route with these lines of code: 

```php
Route::group([
    'middleware' => ['api'],
    'prefix' => 'auth'
], function ($router) {
    Route::post('login', [App\Http\Controllers\AuthController::class, 'login']);
Route::post('register', [App\Http\Controllers\AuthController::class, 'register']);
});
```

### Login Demostration in Postman and React app <a name="test"></a>

#### Postman Test

- Run your DataBase.

- Use the following command to run the Laravel server to test the API: `php artisan serve`

- In Postman, make a POST HTTP request to this route `http://127.0.0.1:8000/api/auth/register` (in case you specified a different port, change it with the one you are using) with a request body containing a username and password, like this:


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vq5l2sr0qazeedtcf6gf.JPG)

- Login with the new user that was created with a POST HTTP request to the route `http://127.0.0.1:8000/api/auth/login`, by specifying the previously registered username and password in the request body.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/boyph31psoaw9nevaq6y.JPG)

- As you can see, if your credentials are valid, you receive a JSON Web Token (JWT) in response that encodes the roles associated with the user. This token can be used to make authenticated requests to your application.

#### Test with React app

This is a simple React app where there is an Admin page that is accessible only to users with the "Admin" role. Therefore, you need to log in with a user that has this role.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cx9t0zmnzf7xn5md4sde.JPG)

When the login occurs, the token is saved and the role is decoded to determine if the logged-in user can access to the private Admin page.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zt3ybanlhylaavg54osk.JPG)

When someone trying to access the Admin page, the saved authentication information is checked to determine if the user has the Admin role.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/labdq9p7ju27moi6kzf8.JPG)

Now let's try to log in to the page with the user "Test" that we created in Postman testing who does not have the "Admin" role:


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/q4ze0pdend5jdcgv05lm.JPG)


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fjfwywb5ipl6j6ttfe7j.JPG)

We can see that upon logging in, attempting to access the *Admin-Panel* results in a redirect to the Unauthorized page as the JSON Web Token associated with the *Test* user session does not contain the necessary role.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3iikwlivq5v2hgd238wf.JPG)




#### Authors:

`Acttis, Julian Xavier.
Moran, Cristian Leandro.`