JWT User Authentication API with Lumen

December 26, 2020

Contents of this article reposted from https://medium.com/@benlobaugh/jwt-user-authentication-with-lumen-api-ee3b8e69c678

Lumen is a great framework to build an API off of, but it does not come with user authentication or authorization. I needed to create a small API that allowed users to create an account and access the service with a JWT. Quality information on how to pull that off with Lumen is not very well available- this article will provide a single reference point on building a simple user authentication and authorization system with JWTs on Lumen.

In this article I will teach you how to set up user authentication and authorization in Lumen. I will not teach you what Lumen or JWT is. I am going to assume you know what a they are or you would not be reading this article.

If you are following along, the prerequisite for what follows is:

  • Running Lumen project

To see the complete code from this article, visit https://github.com/blobaugh/lumen-api-jwt-auth-example

This example includes a docker-compose.yml file that will get you up and running quickly.

For the JWT portion, we will be utilizing the excellent library from https://github.com/tymondesigns/jwt-auth

Install the JWT Library

We will be utilizing the JWT library by Sean Tymon. The library is installable as a composer package, and can be installed with the following command:

composer require tymon/jwt-auth

A secret needs to be generated to configure the JWT library, and added to the .env file. It can be generated with the following artisan command.

php artisan jwt:secret

The .env file was automatically updated with the key. The key will be used to sign all the JWT tokens.

Prep Lumen

There are now some steps we need to take to prep Lumen, before we can implement the user authentication portion.

To begin, open up the file bootstrap/app.php, then add or uncomment the following:

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

Add the JWT Service Provider

$app->register(Tymon\JWTAuth\Providers\LumenServiceProvider::class);

Set up the Auth Config

This part bit me at first- Lumen does not come with the config directory like Laravel does. You will need to create it and a file called auth.php.

Create the file config/auth.php and add the following:

<?phpreturn [
'defaults' => [
'guard' => 'api',
'passwords' => 'users',
],'guards' => [
'api' => [
'driver' => 'jwt',
'provider' => 'users',
],
],'providers' => [
'users' => [
'driver' => 'eloquent',
'model' => \App\Models\User::class
]
]
];

Create the user table

Lumen does not come with the user tables out of the box, so we will need to create them ourselves. We are going to create the same tables that Laravel uses.

Run the following two artisan commands to generate the migration files:

php artisan make:migration create_users_tablephp artisan make:migration create_password_resets_table

Place the following code in the up() method of the create_users_table migration:

Schema::create('users', function (Blueprint $table) {  $table->increments('id');  $table->string('name');  $table->string('email')->unique();  $table->timestamp('email_verified_at')->nullable();  $table->string('password');  $table->rememberToken();  $table->timestamps();});

Place the following code in the up() method of the create_password_resets_table migration:

Schema::create(‘password_resets’, function (Blueprint $table) {

Schema::create('password_resets', function (Blueprint $table) {  $table->string('email')->index();  $table->string('token');  $table->timestamp('created_at')->nullable();});

Finally, run the migrate command to create the tables!

php artisan migrate

Set up the User Model

The User model will allow us to create a representation of the user that can manipulate the database table, and manage a user’s JWT tokens.

Open your User model and add the following use statement:

use Tymon\JWTAuth\Contracts\JWTSubject;

Add JWTSubject to the class implements clause, which will make it similar to:

class User extends Model implements AuthenticatableContract, AuthorizableContract, JWTSubject

Now add the following methods for JWT handling

/**
* Retrieve the identifier for the JWT key.
*
* @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 [];
}

Create Authentication Controller

The Authentication controller will handle both the registration of new users and creation/refreshing of the JWT tokens.

I am not going to go through the AuthController line by line. It will be similar to any other auth controller, with a few tweaks for the JWTs.

Create the app/Http/Controllers/AuthController.php file and place in the following code:

<?php
namespace App\Http\Controllers;use Illuminate\Http\Request;
use App\Models\User;
use Illuminate\Support\Facades\Auth;class AuthController extends Controller
{ public function __construct() {
$this->middleware(‘auth’, [‘except’ => [‘login’, ‘register’, ]]);
} /**
* Attempt to register a new user to the API.
*
* @param Request $request
* @return Response
*/ public function register(Request $request)
{
// Are the proper fields present?
$this->validate($request, [
‘name’ => ‘required|string|between:2,100’,
‘email’ => ‘required|string|email|max:100|unique:users’,
‘password’ => ‘required|string|min:6’,
]); try {
$user = new User;
$user->name = $request->input(‘name’);
$user->email = $request->input(‘email’);
$plainPassword = $request->input(‘password’);
$user->password = app(‘hash’)->make($plainPassword);
$user->save(); return response()->json([‘user’ => $user, ‘message’ => ‘CREATED’], 201);
} catch (\Exception $e) {
return response()->json([‘message’ => ‘User Registration Failed!’], 409);
}
} /**
* Attempt to authenticate the user and retrieve a JWT.
* Note: The API is stateless. This method _only_ returns a JWT. There is not an
* indicator that a user is logged in otherwise (no sessions).
*
* @param Request $request
* @return Response
*/
public function login(Request $request)
{
// Are the proper fields present?
$this->validate($request, [
‘email’ => ‘required|string’,
‘password’ => ‘required|string’,
]); $credentials = $request->only([‘email’, ‘password’]); if (! $token = Auth::attempt($credentials)) {
// Login has failed
return response()->json([‘message’ => ‘Unauthorized’], 401);
} return $this->respondWithToken($token);
} /**
* Log the user out (Invalidate the token). Requires a login to use as the
* JWT in the Authorization header is what is invalidated
*
* @return \Illuminate\Http\JsonResponse
*/
public function logout() {
auth()->logout();
return response()->json([‘message’ => ‘User successfully signed out’]);
} /**
* Refresh the current token.
*
* @return \Illuminate\Http\JsonResponse
*/
public function refresh() {
return $this->respondWithToken( auth()->refresh() );
} /**
* Helper function to format the response with the token.
*
* @return \Illuminate\Http\JsonResponse
*/
private function respondWithToken($token)
{
return response()->json([
‘token’ => $token,
‘token_type’ => ‘bearer’,
‘expires_in’ => Auth::factory()->getTTL() * 60
], 200);}
}

Set up the Routes

Next up is some routing! We are almost done!

Open up the routes/web.php file and add the following to it:

/*
*
* UNAUTHENTICATED ROUTES
*
*/
$router->post( ‘/login’, ‘AuthController@login’);
$router->post( ‘/register’, ‘AuthController@register’ );/*
*
* AUTHENTICATED ROUTES
*
*/
$router->group(
[
‘middleware’ => ‘auth’,
], function( $router ) {
$router->post( ‘/logout’, ‘AuthController@logout’ );
$router->get( ‘/refresh’, ‘AuthController@refresh’ );
$router->post( ‘/refresh’, ‘AuthController@refresh’ );
});

Note: You can do this in routes/api.php if you would like. That will cause a prefix of `api/` in the URL

Congrats

That is it! You now have user authentication and authorization set up on your Lumen API. Congrats!

Leave a Reply

Your email address will not be published. Required fields are marked *