Laravel REST API with JWT Authentication for Login and Role-based permissions

leanm

LeanM

Posted on February 8, 2023

Laravel REST API with JWT Authentication for Login and Role-based permissions

Table of contents

Introduction

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.

  • 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

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:



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


Enter fullscreen mode Exit fullscreen mode
  • 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:


class Role extends Model
{
    use HasFactory;

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

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


Enter fullscreen mode Exit fullscreen mode
  • 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:



//Outside class scope
use DB;

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


Enter fullscreen mode Exit fullscreen mode
  • Modify the up function in User and Role Migration classes to configure the schema of the database:

User Migration



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


Enter fullscreen mode Exit fullscreen mode

Role Migration



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();
        });
    }


Enter fullscreen mode Exit fullscreen mode

Introduce JWT-Auth Library

  • 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

  • Modify the guards array to looks like this:

Image description

Using JWT-Auth Library

Configure User Model

  • Import JWTSubject in User Model class and make this class implement JWTSubject:


use Tymon\JWTAuth\Contracts\JWTSubject;

class User extends Authenticatable implements JWTSubject


Enter fullscreen mode Exit fullscreen mode
  • Add these two methods:


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


Enter fullscreen mode Exit fullscreen mode

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



public function getJWTCustomClaims() {
        return [];
    }  


Enter fullscreen mode Exit fullscreen mode

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:



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
        ]);
    }
}


Enter fullscreen mode Exit fullscreen mode

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:



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']);
});

Enter fullscreen mode Exit fullscreen mode




Login Demostration in Postman and React app

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

  • 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

  • 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

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

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

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

Image description

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

Authors:

Acttis, Julian Xavier.
Moran, Cristian Leandro.

💖 💪 🙅 🚩
leanm
LeanM

Posted on February 8, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related