JWT Authentication and Role-Based Authorization on Laravel 5.6 and jwt-auth.

Chidozie Ogbo
Feb 4 · 5 min read

Laravel is a really nice framework for building REST APIs and one of the most important parts of REST APIs is Authentication and Authorization. Most tutorials walk you through setting up the authentication only but most use-cases require opening up some routes to a specific set of users (Authorization). This post will walk you through setting up JWT Authentication and Role-Based Authorization using Laravel Restful API using jwt-auth, a 3rd party package without much stress.

N.B: It’s assumed that you have a pretty good knowledge of the use of Laravel.

Firstly, create a laravel project. In a case where you don’t know how, simply type.

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

Installation and Configuration of jwt-auth

After setting up your project, you can install the jwt-auth package as stated below.

Edit your composer.json to require the package.

"require": {
"tymon/jwt-auth": "0.5.*"
}

Then run composer update in your terminal to pull it in.

Once this has finished, you will need to add the service provider to the providers array in your app.php config as follows:

Tymon\JWTAuth\Providers\JWTAuthServiceProvider::class

Next, also in the app.php config file, under the aliases array, you may want to add the JWTAuth and JWTFactory facade.

'JWTAuth' => Tymon\JWTAuth\Facades\JWTAuth::class,
'JWTFactory' => Tymon\JWTAuth\Facades\JWTFactory::class,

Also, you will want to publish the config using

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

If that doesn’t generate a config/jwt.php file, then try just.

$ php artisan vendor:publish

and when shown options, select the right one (i.e. “Tymon\JWTAuth…”).

You can configure your jwt.php as stated here.

Finally, to generate a random key that would be used to sign your tokens, run

$ php artisan jwt:generate

If this throws an exception about “undefined method handle….”, then go to vendor/tymon/jwt-auth/src/Commands/JWTGenerateCommand and rename the fire method to handle and run again. This is a temporary hack till the issue is fixed.

Your are all set to use JWT in your application.

Setup Login Route

You can either use your Auth/LoginController or in my case, create a new AuthController. I would be using the later.

$ php artisan make:controller AuthController

In your AuthController.php, add this or if you are using the default LoginController, override the login method with the one below.

use Tymon\JWTAuth\Facades\JWTAuth;
...
class AuthController extends Controller
{
....
public function login(Request $request){
$credentials = $request->only(['email', 'password']);

if (!$token = JWTAuth::attempt($credentials)) {
return 'Invalid login details';
}

return $token;
}
}

This gets your email and password from the request, then validates your details and if correct, it generates and returns a token, else it returns an error message.

If you wish to get other components of the token like the expiry date and other claims stated here, do this.

$payload = JWTAuth::getPayload($token);
$expiry = $payload['exp'];

You can add your route to your routes/api.php file. Be careful to use the correct controller

Route::post('auth/login', ['uses' => 'AuthController@login', 'as' => 'login']);

Then try accessing your route using Postman. If all works well, you should get your token if login details are correct and an error message otherwise.

Setting up your Database

I would be using the simplest database structure possible. And this involves adding a “role” column to you Users table. So, firstly modify your default users migration file by adding just one line.

Schema::create('users', function (Blueprint $table) {
....
$table->string('role');
....
});

Then, run

$ php artisan migrate

After that’s done, you are ready to go. Create a default user on your database which we would use to test our system.

Setting up Authorization

This is actually the easy part. You could do this instead of trying to find your way across a sea of guards, etc. Basically, what we would be doing is

  • Create a middleware
  • Inside your middleware, grab the token from the request.
  • Authenticate your user with the token.
  • Confirm your user is in the specified role.
  • Redirect user to correct route

Create a new middleware using

artisan make:middleware RoleAuthorization

Once your middleware is created, modify the handle method as follows.

use Tymon\JWTAuth\Facades\JWTAuth;
use Tymon\JWTAuth\Exceptions\JWTException;
use Tymon\JWTAuth\Exceptions\TokenExpiredException;
use Tymon\JWTAuth\Exceptions\TokenInvalidException;
...
public function handle($request, Closure $next, ...$roles)
{
try {
//Access token from the request
$token = JWTAuth::parseToken();
//Try authenticating user
$user = $token->authenticate();
} catch (TokenExpiredException $e) {
//Thrown if token has expired
return $this->unauthorized('Your token has expired. Please, login again.');
} catch (TokenInvalidException $e) { //Thrown if token invalid
return $this->unauthorized('Your token is invalid. Please, login again.');
}catch (JWTException $e) { //Thrown if token was not found in the request.
return $this->unauthorized('Please, attach a Bearer Token to your request');
}
//If user was authenticated successfully and user is in one of the acceptable roles, send to next request.
if ($user && in_array($user->role, $roles)) {
return $next($request);
}

return $this->unauthorized();
}

private function unauthorized($message = null){
return response()->json([
'message' => $message ? $message : 'You are unauthorized to access this resource',
'success' => false
], 401);
}

The variable argument $roles gives you the ability to give more than one role access to a particular route and can is used as an array inside the function.

The handle function tries to access the token from the request and then authenticate the user and if it fails at any point, the proper catch block will take care of it and return a json error object to the user, else the user would be sent to the request would continue to the next phrase.

Finally, you would have to register the new middleware in your app/Http/Kernel.php by adding this line to your $routesMiddleware array.

'auth.role' => \App\Http\Middleware\RoleAuthorization::class,

You can call it whatever you like but remember the name you give it. Personally, I prefer to use “auth.role” or “api.role”.

Usage

To use the new middleware, you can either add it when defining your routes in routes/api.php as

Route::get('products', ['middleware' => 'auth.role:admin,user', 'uses' => 'ProductController@index', 'as' => 'products']);

Or you could define it in your controller constructor as.

public function __construct()
{
$this->middleware('auth.role:admin');
}

Also, remember that like every other laravel middleware, you could

public function __construct()
{
$this->middleware('auth.role:admin', ['except' => ['login']]);
//OR
$this->middleware('auth.role:admin', ['only' => ['blockUser']]);
}

To test, create a new controller, UsersController

artisan make:controller UsersController

When that is done, edit the controller and add these methods.

public function __construct()
{
$this->middleware('auth.role:admin', ['only' => ['blockUser']]);
}
public function blockUser()
{
return 'This is an admin route.';
}
public function profile()
{
return 'This route is for all users.';
}

Create the following routes.

Route::get('users/profile', 'uses' => 'UsersController@profile', 'as' => 'users.profile']);
Route::get('users/block', 'uses' => 'UsersController@blockUser', 'as' => 'users.block']);

Then, give your default user a role of ‘admin’ from the database, login and access both routes. Both routes should work successfully.

After that, give your user a role of ‘user’ instead from the database and try again. Only the profile route should be able to work.

NB: After logging in, take your newly acquired token and add it to the header of your request i.e.

Authorization: Bearer <token>

eg: Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.e30.XmNK3GpH3Ys_7wsYBfq4C3M6goz71I7dTgUkuIa5lyQ

If you are using Postman, go to the Authorization tab, select Bearer Token in type and add your token to the ‘Token’ field.

That’s all. I am hopeful that if you are reading this, you now know how to implement JWT Authentication and Role Based Authorization using Laravel 5.6 and jwt-auth package.

Thanks for reading.
Your comments are welcome.

Chidozie Ogbo

Written by

Web/Mobile Developer, UX Enthusiast and Habitual Thinker.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade