This post will go over how to include JWT (Json Web Token) authentication into an existing Lumen API. JWT is a standard for API authentication that is URL safe, and can pass manage small amounts of state too. You may have to edit more files depending on what state your API is already in but if you have an existing API you should be able to manage this. It is assumed that the existing api is just the result of creating a basic Lumen project with the composer tool.

Database Setup

The first part of this tutorial is to setup a user model. In Lumen, these models go in the directory app/Models. So create a new file called User.php

<?php

namespace App\Models;

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;

    protected $table = 'users';
    protected $attributes = [
        'admin' => false, 
    ];
    protected $fillable = [
        'username', 'password', 'added', 'admin',
    ];
    protected $hidden = [
        'password',
    ];

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

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

Now we have a model we need to create the migration for our database. This will actually create the table in the database. Once this file has been created you can migrate the database using php artisan migrate

// php artisan make:migration create_users_table
// Then edit the resulting file to look like so
<?php

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

class CreateUsersTable extends Migration
{
    public function up()
    {
        Schema::create('users', function (Blueprint $table) {
            $table->increments('id');
            $table->string('username');
            $table->string('password');
            $table->boolean('admin')->default(false);
            $table->timestamps();
        });
    }

    public function down()
    {
        Schema::dropIfExists('users');
    }
}

Auth Controller

The auth controller handles all the logic of logging in, logging out and identifying users. Controllers exist in the folder app/Http/Controllers.

Login

The login route sits behind the url /api/auth/login. This route requires a username and password posted to it and uses the Auth facade (from the Illuminate package) to attempt to log the user in to the system. If the login succeeds this function will return back a valid Json Web Token. This token should then be used on subsequent requests (that require the authenticate middleware)

Logout

This route essentially blacklists the user token so if the token is used again it is rejected. It is then up to the client to discard the token and retrieve a new one from the login route..

Me

This route returns the user object that is tied to the used token. This includes the username, admin status and create/edit time

// app/Http/Controllers/AuthController.php
<?php
namespace App\Http\Controllers;

use Illuminate\Support\Facades\Auth;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;

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

    public function respondWithToken($token)
    {
        // Return a token response to the user
        return response()->json([
            'token' => $token,
            'token_type' => 'bearer',
            'expires_in' => 3600
        ], 200);
    }

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

        if (!$token = Auth::attempt($credentials)) {
            return response()->json(['message' => 'Unauthorized'], 401);
        }
        return $this->respondWithToken($token);
    }

    public function logout()
    {
        // TODO: Add the token to a blacklist here
        return response()->json(['message'=>'Goodbye!'], 200);

    }

    public function me()
    {
        return response()->json(auth()->user());
    }
}

Setting Up JWT

First up we need to install the JWT library using composer

composer require tymon/jwt-auth:dev-develop

php artisan jwt:secret

Next up we need to load the JWT authentication module into our app. This is done as a part of the bootrapping and so we need to create a config file, config/auth.php, and edit the app bootstrap, bootstrap/app.php

// config/auth.php

<?php

use App\Models\User;

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

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

    'providers' => [
        'users' => [
            'driver' => 'eloquent',
            'model' => User::class,
        ],
    ],
];
// bootstrap/app.php

// Add this line to the middlewares section
$app->routeMiddleware([
    // ... other middlewares may be laoded here
    'auth' => App\Http\Middleware\Authenticate::class,
]);

// Register the service providers
// uncomment this line
$app->register(App\Providers\AuthServiceProvider::class);
// add this one
$app->register(Tymon\JWTAuth\Providers\LumenServiceProvider::class);

Create API routes

Now that all the middlewares and controllers are setup we can create the API routes that will actually allow user to login and logout etc.

// routes/web.php
$router->group(['prefix' => 'api'], function () use ($router) {
    $router->group(['prefix' => 'auth'], function () use ($router) {
        $router->post('/login', ['as' => 'login', 'uses' => 'AuthController@login']);
        $router->post('/logout', ['as' => 'logout', 'uses' => 'AuthController@logout']);
    });
    $router->group(['prefix' => 'auth', 'middleware' => 'auth'], function() use ($router) {
        $router->get('/me', ['as' => 'me', 'uses' => 'AuthController@me']);
    });
});

Testing

We can test that our API works by using curl.

curl -Lv \
    -H "Content-Type: application/json" \
    -H "Accept: application/json" \
    -d '{"username": "test", "password": "test}' \
    http://localhost/api/auth/login

{
    'token': 'some massive long string',
    'token_type': 'bearer',
    'expires_in': 3600
}
TOKEN=<token_copied_from_above_output>
curl -Lv \
    -H "Content-Type: application/json" \
    -H "Accept: application/json" \
    -H "Authorization: Bearer $TOKEN" \
    http://localhost/api/auth/me

{
    'username': 'test',
    'added': 'datetime string',
    'admin': false
}