Getting started with Lumen 7.0.x and JWT authentication

G

Lumen is the perfect solution for building micro-services based on PHP. In this tutorial, we will build a simple and secure REST API. At the end of this tutorial, you should be able to build production-ready JWT (authentication) template for lumen which will help you build your own APIs. So, let’s get started!

In this tutorial we will use WAMP to host our micro-service. We assume that you have already installed the composer and you are familiar with it. Make sure you have also installed lumen CLI using the following CMD command.

composer global require "laravel/lumen-installer"

Installation of Lumen framework

  • Navigate to the www directory of WAMP and delete any file/folder resides there:
C:\wamp64\www
  • Open CMD there and type (also the dot)
composer create-project --prefer-dist laravel/lumen .
  • Create a new database and assing a user using the phpmyadmin. For this example our database the user and the password will named homestead. Afterwards open the .env file found in the root directory of the lumen framework and assign the correct fields like below:
    (APP_KEY, DB_*) Beware that the APP_KEY should be a random string and will ensure the security of the application.
APP_NAME=Lumen
APP_ENV=local
APP_KEY=eyJ0eXai34567AQiLCJhbGciOiJIUzI1NiJ9
APP_DEBUG=true
APP_URL=http://localhost
APP_TIMEZONE=UTC

LOG_CHANNEL=stack
LOG_SLACK_WEBHOOK_URL=

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=homestead
DB_USERNAME=homestead
DB_PASSWORD=homestead

CACHE_DRIVER=file
QUEUE_CONNECTION=sync
  • Create a .htaccess file (below) in the root directory of the framework to point the public/ floder as initial path and allow the lumen framwork function correctly.
RewriteEngine On
RewriteRule ^(.*)$ public/$1 [L]
  • Check if lumen is actually working correcty: Open your preferred browser and go to
    http://localhost . If everything is ok, you should be able to see:
Lumen (7.0.5) (Laravel Components ^7.0)

Install required packages

  • To complete our tutorial we will need some extra packages to be installed. The configuration of the packages will be done later on in this article. By using the following two CMD commands you will have all the dependecies that will be needed:
composer require chuckrincon/lumen-config-discover
composer require tymon/jwt-auth:dev-develop
  • Afterwards, we need to create a new folder in our root directory named config. In this folder we can easily add all the configuration files we would like to use and thanks to the config-discover package, the framework will automatically load and use them. In our root directory we need to use the following command:
mkdir config
  • To make things work we need alter a bit the /bootstrap/app.php file.
    – Activate the Eloquent and Facades.
    – Register the Authentication middleware.
    – Register the App, Auth, Event, Tymon\JWTAuth, Chuckrincon\LumenConfigDiscover service providers.
<?php

require_once __DIR__.'/../vendor/autoload.php';

(new Laravel\Lumen\Bootstrap\LoadEnvironmentVariables(
    dirname(__DIR__)
))->bootstrap();

date_default_timezone_set(env('APP_TIMEZONE', 'UTC'));

/*
|--------------------------------------------------------------------------
| Create The Application
|--------------------------------------------------------------------------
|
| Here we will load the environment and create the application instance
| that serves as the central piece of this framework. We'll use this
| application as an "IoC" container and router for this framework.
|
*/

$app = new Laravel\Lumen\Application(
    dirname(__DIR__)
);

 $app->withFacades();
 $app->withEloquent();

/*
|--------------------------------------------------------------------------
| Register Container Bindings
|--------------------------------------------------------------------------
|
| Now we will register a few bindings in the service container. We will
| register the exception handler and the console kernel. You may add
| your own bindings here if you like or you can make another file.
|
*/

$app->singleton(
    Illuminate\Contracts\Debug\ExceptionHandler::class,
    App\Exceptions\Handler::class
);

$app->singleton(
    Illuminate\Contracts\Console\Kernel::class,
    App\Console\Kernel::class
);

/*
|--------------------------------------------------------------------------
| Register Config Files
|--------------------------------------------------------------------------
|
| Now we will register the "app" configuration file. If the file exists in
| your configuration directory it will be loaded; otherwise, we'll load
| the default version. You may register other files below as needed.
|
*/

$app->configure('app');

/*
|--------------------------------------------------------------------------
| Register Middleware
|--------------------------------------------------------------------------
|
| Next, we will register the middleware with the application. These can
| be global middleware that run before and after each request into a
| route or middleware that'll be assigned to some specific routes.
|
*/

// $app->middleware([
//     App\Http\Middleware\ExampleMiddleware::class
// ]);

 $app->routeMiddleware([
     'auth' => App\Http\Middleware\Authenticate::class,
 ]);

/*
|--------------------------------------------------------------------------
| Register Service Providers
|--------------------------------------------------------------------------
|
| Here we will register all of the application's service providers which
| are used to bind services into the container. Service providers are
| totally optional, so you are not required to uncomment this line.
|
*/

 $app->register(App\Providers\AppServiceProvider::class);
 $app->register(App\Providers\AuthServiceProvider::class);
 $app->register(App\Providers\EventServiceProvider::class);
 $app->register(Tymon\JWTAuth\Providers\LumenServiceProvider::class);
 $app->register(Chuckrincon\LumenConfigDiscover\DiscoverServiceProvider::class);

/*
|--------------------------------------------------------------------------
| Load The Application Routes
|--------------------------------------------------------------------------
|
| Next we will include the routes file so that they can all be added to
| the application. This will provide all of the URLs the application
| can respond to, as well as the controllers that may handle them.
|
*/

$app->router->group([
    'namespace' => 'App\Http\Controllers',
], function ($router) {
    require __DIR__.'/../routes/web.php';
});

return $app;
  • Generate the JWT secret by typing the following command in the CMD
php artisan jwt:secret

Add configuration files

  • In our example we will need to create the auth.php configuration file in the /config folder
<?php

return [
    'defaults' => [
        'guard' => 'api',
        'passwords' => 'users',
    ],

    'guards' => [
        'api' => [
            'driver' => 'jwt',
            'provider' => 'users',
        ],
    ],

    'providers' => [
        'users' => [
            'driver' => 'eloquent',
            'model' => \App\User::class
        ]
    ]
];

Create users table

  • At the time of this writing, Lumen supports four database systems. In this tutorial we are making use of MySQL. Let’s create a migration for the userstable where all the basic information regarding the users and the token authorization will be held. With the following command a new file at the database/migrations will be created.
php artisan make:migration create_users_table
  • Now we need to fill the generated file inside the folder database/migrations with the data fields we want.
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateUsersTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('users', function (Blueprint $table) 
        {
	    $table->increments('id');
            $table->string('username')->unique();
            $table->string('password');
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('users');
    }
}
  • And to proceed with the table creation we have to initiate the migration using the following command:
php artisan migrate
  • Afterwards we will need to alter the /app/User.php class as follows:
<?php

namespace App;

use Illuminate\Auth\Authenticatable;
use Illuminate\Contracts\Auth\Access\Authorizable as AuthorizableContract;
use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract;
use Illuminate\Database\Eloquent\Model;
use Laravel\Lumen\Auth\Authorizable;
use Tymon\JWTAuth\Contracts\JWTSubject;


class User extends Model implements AuthenticatableContract, AuthorizableContract, JWTSubject
{
    use Authenticatable, Authorizable;
   
    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = [
        'username',
    ];

    /**
     * The attributes excluded from the model's JSON form.
     *
     * @var array
     */
    protected $hidden = [
        'password',
    ];
	
     /**
     * 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 [];
    }
}

Implement the Controllers

  • Now, we have to alter the \app\Http\Controllers\AuthController.php, and include the registration and login methods as well as any other method that we would like to use.
<?php

namespace App\Http\Controllers;

use Illuminate\Support\Facades\Auth;
use Illuminate\Http\Request;
use App\User;

class AuthController extends Controller
{
    public function __construct()
    {
        $this->middleware('auth:api', ['except' => ['login','register']]);
    }

    /**
     * Store a new user.
     *
     * @param  Request  $request
     * @return Response
     */
    public function register(Request $request)
    {
        //validate incoming request 
        $this->validate($request, [
            'username' => 'required|string|unique:users',
            'password' => 'required|confirmed',
        ]);

        try 
        {
            $user = new User;
            $user->username= $request->input('username');
            $user->password = app('hash')->make($request->input('password'));
            $user->save();

            return response()->json( [
                        'entity' => 'users', 
                        'action' => 'create', 
                        'result' => 'success'
            ], 201);

        } 
        catch (\Exception $e) 
        {
            return response()->json( [
                       'entity' => 'users', 
                       'action' => 'create', 
                       'result' => 'failed'
            ], 409);
        }
    }
	
     /**
     * Get a JWT via given credentials.
     *
     * @param  Request  $request
     * @return Response
     */	 
    public function login(Request $request)
    {
          //validate incoming request 
        $this->validate($request, [
            'username' => 'required|string',
            'password' => 'required|string',
        ]);

        $credentials = $request->only(['username', 'password']);

        if (! $token = Auth::attempt($credentials)) {			
            return response()->json(['message' => 'Unauthorized'], 401);
        }
        return $this->respondWithToken($token);
    }
	
     /**
     * Get user details.
     *
     * @param  Request  $request
     * @return Response
     */	 	
    public function me()
    {
        return response()->json(auth()->user());
    }
}
  • Moreover, we have to alter the \app\Http\Controllers\Controller.php, and include the global method respondWithToken.
<?php

namespace App\Http\Controllers;

use Laravel\Lumen\Routing\Controller as BaseController;
use Illuminate\Support\Facades\Auth;
  
class Controller extends BaseController
{
    public function respondWithToken($token)
    {
        return response()->json([
            'token' => $token,
            'token_type' => 'bearer',
            'expires_in' => null
        ], 200);
    }
}

Add routes

  • Now that we have the users table we need to provide some basic functionalities. The most vitals are the users registration/login. As indicated in the previous code block, AuthController is our controller class and the register, login, me are methods of that class. To add them in our routes list, locate routes/web.php and insert the needed code as seen below:
<?php

/*
|--------------------------------------------------------------------------
| Application Routes
|--------------------------------------------------------------------------
|
| Here is where you can register all of the routes for an application.
| It is a breeze. Simply tell Lumen the URIs it should respond to
| and give it the Closure to call when that URI is requested.
|
*/

$router->get('/', function () use ($router) {
    return $router->app->version();
});

$router->group(['middleware' => 'auth','prefix' => 'api'], function ($router) 
{
    $router->get('me', 'AuthController@me');
});

$router->group(['prefix' => 'api'], function () use ($router) 
{
   $router->post('register', 'AuthController@register');
   $router->post('login', 'AuthController@login');
});

Validate our implementation

  • To validate our implementation we can use POSTMAN. As i first step we will validate the registration like the following screenshot:
  • Secondly, we will validate the login:
  • Finally, we can test the me endpoint (and ensure that only authorized access is allowed)
Unauthorized access denied
Authorized Access

8 comments

  • hello,
    i’m getting ” Object not found! The requested URL was not found on this server. ” when I try Postman. happens with all the routes.

    • Hi, Could you please verify that you performed all the mentioned steps correctly and give me a bit more info about your issue? If you open your browser and type http://localhost do you get the lumen version “Lumen (7.0.5) (Laravel Components ^7.0)” ?

  • Hello, I tested this api using postman, but it is reporting this error at api/login:

    I’m use laravel/lumen-framework (v7.1.4).
    Please help

  • Hello, I tested this api using postman, but it is reporting this error at api/login:
    “Argument 1 passed to Tymon\JWTAuth\JWTGuard::login() must be an instance of Tymon\JWTAuth\Contracts\JWTSubject, instance of App\User given, called in H:\sourcecode\lumen7-jwt\vendor\tymon\jwt-auth\src\JWTGuard.php on line 127 (500 Internal Server Error)”
    I’m use laravel/lumen-framework (v7.1.4).
    Please help

    • Hello, this tutorial is based on Lumen version 7.0.5 and i sincerely do not know if the 7.1.x version is compatible. When i have some free time i will test the 7.1.x version and i’ll let you know. Thanks

  • Hello, apologies for late reply, turned out that it’s related with Arch Linux’s apache mod_write configurations. I must have configured something wrongly or missed something in the configs. I moved the project to Ubuntu and it’s working flawlessly. Thank you so much.

Categories

Tags