Build a JWT Authenticated API with Lumen(v5.8)
(Source/Credits: https://dev.to/ndiecodes/build-a-jwt-authenticated-api-with-lumen-2afm)
In this tutorial, we will be using lumen; a super-fast micro-framework by laravel to build a simple and secure REST API. At the end of this tutorial, you should be able to build production-ready APIs. Let's get started!
title: Build a JWT Authenticated API with Lumen(v5.8) published: true description: In this tutorial, we will be using lumen; a super-fast micro-framework by laravel to build a simple and secure REST API. At the end of this tutorial, you should be able to build production-ready APIs. Let's get started! tags: php,lumen,laravel,webDev canonical_url: https://voidnerd.com/posts/build-a-jwt-authenticated-api-with-lumen
In this tutorial, we will be using lumen; a super-fast micro-framework by laravel to build a simple and secure REST API. At the end of this tutorial, you should be able to build production-ready APIs. Let's get started!
Prerequisite
Make sure you have the essentials, I beg of you.
- PHP >= 7.1.3
- OpenSSL PHP Extension
- PDO PHP Extension
- Mbstring PHP Extension
- Mysql >= 5.7
- Composer (Dependency Manager for PHP)
- Postman (To test your endpoints)
Installation
First things first, you need to get lumen's cli.
$ composer global require "laravel/lumen-installer"
If your download was successful, run below command to confirm you have lumen installed.
$ lumen
If you encounter an error like -bash: lumen: command not found
, you need to add composer's vender bin to your path.
If all is in order, you should see something like so.
Now run this command to create the lumen project
$ lumen new auth-app
Enter the project folder
$ cd auth-app
Run the app
$ php -S localhost:8000 -t public
Load localhost:8000
on your browsers address bar and it should render a result as shown below.
Open up the project (auth-app) in your preferred editor.
Create a .env file, copy all contents in .env.example into the .env file and add your database configurations.
In boostrap/app.php
uncomment the facades and eloquent method
```php //before
// $app->withFacades();
// $app->withEloquent();
//after
$app->withFacades();
$app->withEloquent();
``` Turning on withFacades inject the application IoC to Illuminate\Support\Facades\Facade. Without doing so even if you're importing Illuminate\Support\Facades\File it wouldn't work. Credit
The $app->withEloquent() method actually enables the query builder too. It's registering the DatabaseServiceProvider, which is required to use the query builder. Credit.
Create a user
Make user's database migration
$ php artisan make:migration create_users_table --create=users
Locate the migration file database/migrations/*_create_users_table.php
and add neede table columns(name, email, password); see code below:
php
...
public function up()
{
Schema::create('users', function (Blueprint $table) {
$table->bigIncrements('id');
$table->string('name');
$table->string('email')->unique()->notNullable();
$table->string('password');
$table->timestamps();
});
}
...
Migrate your database
``` $ php artisan migrate
```
Add register route which as the name implies; register users. Locate routes/web.php
and insert the needed code as seen below
```php // API route group $router->group(['prefix' => 'api'], function () use ($router) { // Matches "/api/register $router->post('register', 'AuthController@register');
}); ```
Since we are going to prefix api
in all our endpoint, to reduce repetition we will use route grouping to do just that.
This method ($router->post($uri, $callback);
takes in a $url and a $callback parameter. In the $callback
, AuthController
is our controller class (we will create this class in a bit) and register
is a method in said class.
Let's create our AuthControler.
Create file app/Http/Controllers/AuthController.php
and populate it with code as seen below.
```php
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request; use App\User;
class AuthController extends Controller { /* * Store a new user. * * @param Request $request * @return Response / public function register(Request $request) { //validate incoming request $this->validate($request, [ 'name' => 'required|string', 'email' => 'required|email|unique:users', 'password' => 'required|confirmed', ]);
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 successful response
return response()->json(['user' => $user, 'message' => 'CREATED'], 201);
} catch (\Exception $e) {
//return error message
return response()->json(['message' => 'User Registration Failed!'], 409);
}
}
} ```
Register a user(use POSTMAN) with route localhost:8000/api/register
and you should get a successful response like so
User sign in
Pull in the JWT authentication package.
$ composer require tymon/jwt-auth:dev-develop
Generate your API secret
$ php artisan jwt:secret
create file config/auth.php
with below config
```php //config.auth.php
<?php
return [ 'defaults' => [ 'guard' => 'api', 'passwords' => 'users', ],
'guards' => [
'api' => [
'driver' => 'jwt',
'provider' => 'users',
],
],
'providers' => [
'users' => [
'driver' => 'eloquent',
'model' => \App\User::class
]
]
];
``
Make some changes to your
Usermodel(
app/User.php`) to fit tymon/jwt-auth's requirements. Keep your eye out for everything that includes "JWT".
```php <?php
namespace App;
use Illuminate\Auth\Authenticatable; use Laravel\Lumen\Auth\Authorizable; use Illuminate\Database\Eloquent\Model; use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract; use Illuminate\Contracts\Auth\Access\Authorizable as AuthorizableContract;
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 = [
'name', 'email'
];
/**
* 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 [];
}
}
``
Make some changes to
bootstrap/app.php`
```php //before // $app->routeMiddleware([ // 'auth' => App\Http\Middleware\Authenticate::class, // ]);
//After $app->routeMiddleware([ 'auth' => App\Http\Middleware\Authenticate::class, ]); ```
```php //before // $app->register(App\Providers\AppServiceProvider::class); // $app->register(App\Providers\AuthServiceProvider::class); // $app->register(App\Providers\EventServiceProvider::class);
//After // $app->register(App\Providers\AppServiceProvider::class); $app->register(App\Providers\AuthServiceProvider::class); // $app->register(App\Providers\EventServiceProvider::class);
// Add this line $app->register(Tymon\JWTAuth\Providers\LumenServiceProvider::class); ```
Add login route in routes/web.php
```php // API route group $router->group(['prefix' => 'api'], function () use ($router) { // Matches "/api/register $router->post('register', 'AuthController@register');
// Matches "/api/login
$router->post('login', 'AuthController@login');
}); ```
Add a global respondWithToken method to Controller class in app/Http/Controllers/Controller.php
. This is so we could access it from any other controller.
```php ... //import auth facades use Illuminate\Support\Facades\Auth;
//Add this method to the Controller class protected function respondWithToken($token) { return response()->json([ 'token' => $token, 'token_type' => 'bearer', 'expires_in' => Auth::factory()->getTTL() * 60 ], 200); }
```
Add a login method to your AuthController class in app/Http/Controllers/AuthController.php
```php ...
//import auth facades use Illuminate\Support\Facades\Auth;
...
/**
* Get a JWT via given credentials.
*
* @param Request $request
* @return Response
*/
public function login(Request $request)
{
//validate incoming request
$this->validate($request, [
'email' => 'required|string',
'password' => 'required|string',
]);
$credentials = $request->only(['email', 'password']);
if (! $token = Auth::attempt($credentials)) {
return response()->json(['message' => 'Unauthorized'], 401);
}
return $this->respondWithToken($token);
}
```
Login a user using route localhost:8000/api/login
and you should get a successful response like so:
Authenticated routes
For our grand finale, we are going to make some authenticated routes.
Add a couple of routes to routes/web.php
```php ... // API route group $router->group(['prefix' => 'api'], function () use ($router) { // Matches "/api/register $router->post('register', 'AuthController@register'); // Matches "/api/login $router->post('login', 'AuthController@login');
// Matches "/api/profile
$router->get('profile', 'UserController@profile');
// Matches "/api/users/1
//get one user by id
$router->get('users/{id}', 'UserController@singleUser');
// Matches "/api/users
$router->get('users', 'UserController@allUsers');
});
...
```
Create a file app/Http/Controllers/UserController.php
and populate it with this elegant looking code.
```php <?php
namespace App\Http\Controllers;
use Illuminate\Support\Facades\Auth; use App\User;
class UserController extends Controller { /* * Instantiate a new UserController instance. * * @return void / public function __construct() { $this->middleware('auth'); }
/**
* Get the authenticated User.
*
* @return Response
*/
public function profile()
{
return response()->json(['user' => Auth::user()], 200);
}
/**
* Get all User.
*
* @return Response
*/
public function allUsers()
{
return response()->json(['users' => User::all()], 200);
}
/**
* Get one user.
*
* @return Response
*/
public function singleUser($id)
{
try {
$user = User::findOrFail($id);
return response()->json(['user' => $user], 200);
} catch (\Exception $e) {
return response()->json(['message' => 'user not found!'], 404);
}
}
}
```
Below is an example call to one of the three newly added endpoints
Here's a link to the full code on github.
The end-ish!
I hope this article has helped you in some way, and that you build upon this knowledge to deploy awesome APIs in the nearest future. I would like to see your contributions down in the comments too.
Hasta la vista.
Comments section
wandieinnocents
•May 1, 2024
This has been an amazing tutorial , few bugs but were resolved :)-
alih70442
•May 1, 2024
thanks for sharing this great tutorial.
I had issue with ('model' => \App\User::class).
so I changed it to ('model' => \App\Models\User::class),
and it works now.
devsakib
•May 1, 2024
Thanks for the tutorial, helps me a lot
tadeubdev
•May 1, 2024
Great post! You had save me!
jenueldev
•May 1, 2024
how to do this when using mongodb?
techautobahn
•May 1, 2024
Wonderful Bro.. It really helped me to understand the concept.. Thank you..
jaiminhothi
•May 1, 2024
Hi,
Thanks for your great tutorial.
But I want a custom field (email and password) both so could you make it an example for that.
My field name = USER_NAME, PASSWORD (Both are capital)
I try but token always false.
If you do then its great help for me.
Thank you
racedaemon
•May 1, 2024
This was of great help. The only problem I encountered was when running
php artisan jwt:secret
.$app->register(Tymon\JWTAuth\Providers\LumenServiceProvider::class);
needs to be added to app.php before the command is executed.mayank091193
•May 1, 2024
This was really helpful, man! Thanks very much.
ainurrahms
•May 1, 2024
Does the lumen version affect? I use lumen version 6
be_anjos
•May 1, 2024
Hy bro, thanks for the tutorial. I want to know how I implements logout
mamedioguilherme1
•May 1, 2024
public function logout () {
Auth::logout();
return response()->json(['message' => 'Successfully logged out']);
}
amanuell2
•May 1, 2024
can you go further this post by making role-based auth in lumen?
schallernicolas
•May 1, 2024
What a great tutorial. Thank you very much!
ndiecodes Author
•May 1, 2024
You are welcome
guimilleo
•May 1, 2024
Please, can you explain how to reset the password of user?
ndiecodes Author
•May 1, 2024
Maybe this can help
stackoverflow.com/questions/373380...
isrortega
•May 1, 2024
Thank you, very good tutorial
bluerabby
•May 1, 2024
getting this error when doing login request
"Method Illuminate\Auth\RequestGuard::attempt does not exist."
Any help ?
bluerabby
•May 1, 2024
Sorry, my mistake who put config/auth.php that not in root directory
yunchurlee
•May 1, 2024
I've just reviewed and coded through. It worked wonderfully~~~
wmazed
•May 1, 2024
First of all great tutorial, thanks a lot for all your efforts dude, I've a question, how can I set a remember me system with JWT token ? And also, how to increase expiration duration from the config file (config/auth.php) ?
ndiecodes Author
•May 1, 2024
Would there be a need for remember me? As long as the client has the token; they have access to the system.
You could extend token's time to live for those type of users, that could work...I think.
Override the token ttl
$token = auth()->setTTL(7200)->attempt($credentials);
Further Reading
stackoverflow.com/questions/236038...
wmazed
•May 1, 2024
For the second part of the question, the answer should be:
github.com/tymondesigns/jwt-auth/b...
debabratakarfa
•May 1, 2024
Getting this error,
In Connection.php line 665:
SQLSTATE[HY000] [2002] Connection refused (SQL: create table
migrations
(id
int unsigned not null auto_increment primary key,migration
varchar(255) not null,batch
int notnull) default character set utf8mb4 collate 'utf8mb4_unicode_ci')
In Connector.php line 70:
SQLSTATE[HY000] [2002] Connection refused
Using Homestead development environment.
ndiecodes Author
•May 1, 2024
This error is due to PHP not being able to connect to MySQL
"Create a .env file, copy all contents in .env.example into the .env file and add your database configurations."
bosz
•May 1, 2024
Nice tutorial, very clear and straight to the point. My worry is why the dev-develope branch? The main branch, does it have an issue?
ndiecodes Author
•May 1, 2024
For some reason the dev-develop branch has always worked for new versions of lumen, the next best thing would be version @2.0-dev
guibattoni
•May 1, 2024
Thanks for this article! Very straightforward and easy to understand : )
ndiecodes Author
•May 1, 2024
I'm glad it helped :)
cleathley
•May 1, 2024
you might want to also add
``` use Illuminate\Support\Facades\Auth;
```
when you add
respondWithToken
toapp/Http/Controllers/Controller.php
(Lumin 6+)sayajin101
•May 1, 2024
This saved my life ty
ndiecodes Author
•May 1, 2024
Thanks For this, I will update the tutorial to help people not encounter this error in the future.
kbzone
•May 1, 2024
This is key.
I was followed step by step your tutorial, writing the code by myself (i mean, without clone your github project) and without this line (thanks @cleathley ) an error appears.
I think you should edit your post and add that line after you explain this:
«Add a global respondWithToken method to Controller class»
Anyway, great tutorial. It help me a lot!!
executions12
•May 1, 2024
everything except php artisan jwt::secret is work...
so, i write jwt key in .env by myself..
thank you, nice tutorial...
dhavaldignizant
•May 1, 2024
getting this error after install jwt
php artisan jwt:secret
There are no commands defined in the "jwt" namespace.
b6t3m6n
•May 1, 2024
Had the same issue.
following the docs here jwt-auth.readthedocs.io/en/develop...
especially the chapter Bootstrap file changes fixed the issue
Add the following snippet to the
bootstrap/app.php
file under the providers section as follows:``` // Uncomment this line $app->register(App\Providers\AuthServiceProvider::class);
// Add this line $app->register(Tymon\JWTAuth\Providers\LumenServiceProvider::class);
```
Enter fullscreen mode
Exit fullscreen mode
ndiecodes Author
•May 1, 2024
Please check to make sure
tymon/jwt-auth
installed successfullyAlso type
php artisan
to see available commands.Keep me posted on your progress.
nwaweru
•May 1, 2024
Nice one!
ndiecodes Author
•May 1, 2024
Thanks!
ndiecodes Author
•May 1, 2024
I am yet to come across this issue. Is there an open issue on github?