Recently I have been tinkering with Angular-4 to get a taste of it and I decided to create a quick project to get my hands dirty. I decided to create a blog with authentication etc. My main focus was on the frontend so I decided to quickly bootstrap an application in Lumen because of its simplicity and almost zero-configuration development. For the authentication, I decided to go with JWT and this post is going to be a quick write-up on how I integrated that and how anyone can integrate JWT authentication in their APIs. Firstly if you are not aware of what JWT Authentication is, I would suggest you to go through this nice little article to get the idea.
I hope you are excited so lets get started.
First things first, let's create an empty lumen project. Open up your terminal and run the following command to create a fresh copy of lumen project on your desktop:
composer create-project --prefer-dist laravel/lumen lumen-jwt
Now we need to create the configuration file i.e. .env
at the root of directory. Create it by simply copying the content of .env.example
to .env
:
cd lumen-jwt
cp .env.example .env
Now lets set a few configuration values in our .env
file. Open this lumen project in your favorite code editor or IDE and put the below in the .env
ile:
APP_ENV=local
APP_DEBUG=true
APP_KEY=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
DB_CONNECTION=mysql
DB_HOST=localhost
DB_PORT=3306
DB_DATABASE=
DB_USERNAME=
DB_PASSWORD=
CACHE_DRIVER=file
JWT_SECRET=JhbGciOiJIUzI1N0eXAiOiJKV1QiLC
Note: Remember to set the
APP_KEY
andJWT_SECRET
to your own.
Create a migration file for the users table:
php artisan make:migration create_users_table
Modify the migration file created inside the database/migrations
directory to have name
, email
and password
fields. For me the file is database/migrations/2017_09_05_115448_create_users_table.php
:
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateUsersTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('users', function (Blueprint $table) {
$table->increments('id');
$table->string('name');
$table->string('email')->unique();
$table->string('password');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::drop('users');
}
}
Now let's define some factory methods that will help us in populating some seed data in the database. Open the file database/factories/ModelFactory.php
and modify it to look like below:
<?php
/*
|--------------------------------------------------------------------------
| Model Factories
|--------------------------------------------------------------------------
|
| Here you may define all of your model factories. Model factories give
| you a convenient way to create models for testing and seeding your
| database. Just tell the factory how a default model should look.
|
*/
$factory->define(App\User::class, function (Faker\Generator $faker) {
return [
'name' => $faker->name,
'email' => $faker->unique()->email,
'password' => bcrypt('12345'),
];
});
Now let's create the seeder to populate database with some users. Mmodify database/seeds/UsersTableSeeder.php
to look like:
<?php
use Illuminate\Database\Seeder;
class UsersTableSeeder extends Seeder
{
/**
* Run the database seeds.
*
* @return void
*/
public function run()
{
// create 10 users using the user factory
factory(App\User::class, 10)->create();
}
}
Now let's register this user seeder in our database seeders. Modify database/seeds/DatabaseSeeder.php
to look like this:
<?php
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Seeder;
class DatabaseSeeder extends Seeder
{
/**
* Run the database seeds.
*
* @return void
*/
public function run()
{
Model::unguard();
// Register the user seeder
$this->call(UsersTableSeeder::class);
Model::reguard();
}
}
Now create the configured database in MySQL and run the following commands inside your terminal to create the users table and add some dummy data respectively:
php artisan migrate
php artisan db:seed
If you run into any errors, run the below to make sure that composer knows about these newly created classes
composer dump-autoload
Lumen does not have facades and eloquent enabled by default, let's enable them first by opening the bootstrap/app.php
file and uncommenting the following lines:
$app->withFacades();
$app->withEloquent();
Now let's create the endpoint to generate JWT token. There are tons of libraries out there that will help you with it we will use the one called firebase/php-jwt
. Open up your terminal and run the following command to pull it in using composer:
composer require firebase/php-jwt
Now let's add the endpoint POST /auth/login
that will accept the credentials and return a token for us. Let's register the route first by adding the following route inside routes/web.php
file:
<?php
$app->post('auth/login', ['uses' => 'AuthController@authenticate']);
Now we need the controller AuthController
with method authenticate
. Inside app/Http/Controllers
folder create a new AuthController.php
file and put follwoing content inside it:
In a production ready application you will probably have models and helper methods to achieve this but for the sake of brevity let's put everything inside the controller.
<?php
namespace App\Http\Controllers;
use Validator;
use App\User;
use Firebase\JWT\JWT;
use Illuminate\Http\Request;
use Firebase\JWT\ExpiredException;
use Illuminate\Support\Facades\Hash;
use Laravel\Lumen\Routing\Controller as BaseController;
class AuthController extends BaseController
{
/**
* The request instance.
*
* @var \Illuminate\Http\Request
*/
private $request;
/**
* Create a new controller instance.
*
* @param \Illuminate\Http\Request $request
* @return void
*/
public function __construct(Request $request) {
$this->request = $request;
}
/**
* Create a new token.
*
* @param \App\User $user
* @return string
*/
protected function jwt(User $user) {
$payload = [
'iss' => "lumen-jwt", // Issuer of the token
'sub' => $user->id, // Subject of the token
'iat' => time(), // Time when JWT was issued.
'exp' => time() + 60*60 // Expiration time
];
// As you can see we are passing `JWT_SECRET` as the second parameter that will
// be used to decode the token in the future.
return JWT::encode($payload, env('JWT_SECRET'));
}
/**
* Authenticate a user and return the token if the provided credentials are correct.
*
* @param \App\User $user
* @return mixed
*/
public function authenticate(User $user) {
$this->validate($this->request, [
'email' => 'required|email',
'password' => 'required'
]);
// Find the user by email
$user = User::where('email', $this->request->input('email'))->first();
if (!$user) {
// You wil probably have some sort of helpers or whatever
// to make sure that you have the same response format for
// differents kind of responses. But let's return the
// below respose for now.
return response()->json([
'error' => 'Email does not exist.'
], 400);
}
// Verify the password and generate the token
if (Hash::check($this->request->input('password'), $user->password)) {
return response()->json([
'token' => $this->jwt($user)
], 200);
}
// Bad Request response
return response()->json([
'error' => 'Email or password is wrong.'
], 400);
}
}
Lets open up your terminal and test our authenticate route by sending a post request and check if we are getting token in response.
curl -X POST -F 'email=ziishaned@gmail.com' -F 'password=admin' http://localhost:8080/auth/login
After hitting the route you will get something like following result in response:
{
"token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJibG9nLmRldiIsInN1YiI6IjU5YWEyYTQ3ZjlkYzQxMTU3ODAwNjM0NiIsImlhdCI6MTUwNDYwNTY1MiwiZXhwIjoxNTA0NjA5MjUyfQ.F-7q5gR7TnLFaHxKhOiacgPlOzAYGgQ1lu5mZ_WWnqI"
}
Now we need a middleware to protect our routes. Lets create a middleware JwtMiddleware
inside app/Http/Middleware
folder that will validate provided token and put the following content inside it.
<?php
namespace App\Http\Middleware;
use Closure;
use Exception;
use App\User;
use Firebase\JWT\JWT;
use Firebase\JWT\ExpiredException;
class JwtMiddleware
{
public function handle($request, Closure $next, $guard = null)
{
$token = $request->get('token');
if(!$token) {
// Unauthorized response if token not there
return response()->json([
'error' => 'Token not provided.'
], 401);
}
try {
$credentials = JWT::decode($token, env('JWT_SECRET'), ['HS256']);
} catch(ExpiredException $e) {
return response()->json([
'error' => 'Provided token is expired.'
], 400);
} catch(Exception $e) {
return response()->json([
'error' => 'An error while decoding token.'
], 400);
}
$user = User::find($credentials->sub);
// Now let's put the user in the request class so that you can grab it from there
$request->auth = $user;
return $next($request);
}
}
Open the bootstrap/app.php
file and register our JwtMiddleware
and alias it with jwt.auth
or whatever you like.
$app->routeMiddleware([
'jwt.auth' => App\Http\Middleware\JwtMiddleware::class,
]);
Now let's protect some of our routes. Open the routes file i.e. routes/web.php
and put the following routes inside it:
$app->group(['middleware' => 'jwt.auth'], function() use ($app) {
$app->get('users', function() {
$users = \App\User:all();
return response()->json($users);
});
});
Lets open up your terminal and test if our request succeeds by hitting users route. Run the following command inside your terminal:
curl -X GET http://localhost:8080/users
Output:
{
"error": "Token not provided."
}
Now lets make another request to users route on which we implemented jwt.auth
middleware and this time lets put the token that you will get by hitting authenticate route. Run the following commands inside your terminal:
curl -X POST -F 'email=ziishaned@gmail.com' -F 'password=admin' http://localhost:8080/auth/login
curl -X GET 'http://localhost:8080/api/v1/users?token=tokenFromFirstRequest'
Output:
[
{
"id": 1,
"email": "johndoe@gmail.com",
"updated_at": "2017-09-02 03:49:27",
"created_at": "2017-09-02 03:49:27"
},
{
"id": 2,
"email": "example@example.com",
"updated_at": "2017-09-02 03:49:27",
"created_at": "2017-09-02 03:49:27"
}
]
And that about wraps it up. If you have any questions feel free to leave your comments below.
Top comments (5)
Thanks for this lovely post.
What's happens in the case you want to access the endpoint
api/v1/posts
from your Frontend app to display posts to anyone who visits your web app. Should this route be protected. What's the best way to do this?So nice ! Thanks you
I have a post for sharing!
Angular SpringBoot Jwt Authentication Example:
loizenai.com/angular-10-spring-boo...
Please check it for feedback! Thanks
great article thanks for sharing!
just a little correction, in AuthController.php
function jwt the return value must be
return JWT::encode($payload, env('JWT_SECRET'),'HS256');
in otherwise,when we try to access to a protected route will fail
hello, thanks for your great post, jus one question, in the jwtmiddleware, when you
return $next($request); then how can i retrieve the $request (so the logged user data) in the next controller?
Thank you for the excellent post, this was what I was exactly looking for!!