├── database ├── migrations │ ├── .gitkeep │ ├── 2021_02_26_092051_create_posts_table.php │ └── 2021_02_26_094144_create_users_table.php ├── seeders │ └── DatabaseSeeder.php └── factories │ ├── PostFactory.php │ └── UserFactory.php ├── resources └── views │ └── .gitkeep ├── app ├── Console │ ├── Commands │ │ └── .gitkeep │ └── Kernel.php ├── Events │ ├── Event.php │ └── ExampleEvent.php ├── Http │ ├── Controllers │ │ ├── Controller.php │ │ ├── ExampleController.php │ │ ├── PostController.php │ │ └── AuthController.php │ └── Middleware │ │ ├── ExampleMiddleware.php │ │ └── Authenticate.php ├── Models │ ├── Post.php │ └── User.php ├── Providers │ ├── AppServiceProvider.php │ ├── EventServiceProvider.php │ └── AuthServiceProvider.php ├── Jobs │ ├── ExampleJob.php │ └── Job.php ├── Listeners │ └── ExampleListener.php └── Exceptions │ └── Handler.php ├── storage ├── app │ └── .gitignore ├── logs │ └── .gitignore └── framework │ ├── views │ └── .gitignore │ └── cache │ ├── data │ └── .gitignore │ └── .gitignore ├── .gitignore ├── .styleci.yml ├── .editorconfig ├── .env.example ├── tests ├── TestCase.php └── ExampleTest.php ├── config └── auth.php ├── phpunit.xml ├── public ├── .htaccess └── index.php ├── README.md ├── routes └── web.php ├── artisan ├── composer.json └── bootstrap └── app.php /database/migrations/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resources/views/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/Console/Commands/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /storage/app/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/logs/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/framework/views/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/framework/cache/data/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/framework/cache/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !data/ 3 | !.gitignore 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor 2 | /.idea 3 | Homestead.json 4 | Homestead.yaml 5 | .env 6 | .phpunit.result.cache 7 | -------------------------------------------------------------------------------- /.styleci.yml: -------------------------------------------------------------------------------- 1 | php: 2 | preset: laravel 3 | disabled: 4 | - unused_use 5 | js: true 6 | css: true 7 | -------------------------------------------------------------------------------- /app/Events/Event.php: -------------------------------------------------------------------------------- 1 | call('UsersTableSeeder'); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | APP_NAME=Lumen 2 | APP_ENV=local 3 | APP_KEY= 4 | APP_DEBUG=true 5 | APP_URL=http://localhost 6 | APP_TIMEZONE=UTC 7 | 8 | LOG_CHANNEL=stack 9 | LOG_SLACK_WEBHOOK_URL= 10 | 11 | DB_CONNECTION=mysql 12 | DB_HOST=127.0.0.1 13 | DB_PORT=3306 14 | DB_DATABASE=homestead 15 | DB_USERNAME=homestead 16 | DB_PASSWORD=secret 17 | 18 | CACHE_DRIVER=file 19 | QUEUE_CONNECTION=sync 20 | -------------------------------------------------------------------------------- /tests/TestCase.php: -------------------------------------------------------------------------------- 1 | $this->faker->sentence, 16 | 'body' => $this->faker->paragraph 17 | ]; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /config/auth.php: -------------------------------------------------------------------------------- 1 | [ 5 | 'guard' => 'api', 6 | 'passwords' => 'users', 7 | ], 8 | 9 | 'guards' => [ 10 | 'api' => [ 11 | 'driver' => 'jwt', 12 | 'provider' => 'users', 13 | ], 14 | ], 15 | 16 | 'providers' => [ 17 | 'users' => [ 18 | 'driver' => 'eloquent', 19 | 'model' => \App\Models\User::class 20 | ] 21 | ] 22 | ]; 23 | -------------------------------------------------------------------------------- /tests/ExampleTest.php: -------------------------------------------------------------------------------- 1 | get('/'); 16 | 17 | $this->assertEquals( 18 | $this->app->version(), $this->response->getContent() 19 | ); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/Providers/EventServiceProvider.php: -------------------------------------------------------------------------------- 1 | [ 16 | \App\Listeners\ExampleListener::class, 17 | ], 18 | ]; 19 | } 20 | -------------------------------------------------------------------------------- /app/Listeners/ExampleListener.php: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | ./tests 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /app/Console/Kernel.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | Options -MultiViews -Indexes 4 | 5 | 6 | RewriteEngine On 7 | 8 | # Handle Authorization Header 9 | RewriteCond %{HTTP:Authorization} . 10 | RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] 11 | 12 | # Redirect Trailing Slashes If Not A Folder... 13 | RewriteCond %{REQUEST_FILENAME} !-d 14 | RewriteCond %{REQUEST_URI} (.+)/$ 15 | RewriteRule ^ %1 [L,R=301] 16 | 17 | # Handle Front Controller... 18 | RewriteCond %{REQUEST_FILENAME} !-d 19 | RewriteCond %{REQUEST_FILENAME} !-f 20 | RewriteRule ^ index.php [L] 21 | 22 | -------------------------------------------------------------------------------- /database/factories/UserFactory.php: -------------------------------------------------------------------------------- 1 | $this->faker->name, 26 | 'email' => $this->faker->unique()->safeEmail, 27 | 'password' => app('hash')->make('123456') 28 | ]; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/Jobs/Job.php: -------------------------------------------------------------------------------- 1 | id(); 18 | $table->string('title'); 19 | $table->text('body'); 20 | $table->timestamps(); 21 | }); 22 | } 23 | 24 | /** 25 | * Reverse the migrations. 26 | * 27 | * @return void 28 | */ 29 | public function down() 30 | { 31 | Schema::dropIfExists('posts'); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /database/migrations/2021_02_26_094144_create_users_table.php: -------------------------------------------------------------------------------- 1 | id(); 18 | $table->string('name'); 19 | $table->string('email')->unique(); 20 | $table->string('password'); 21 | $table->timestamps(); 22 | }); 23 | } 24 | 25 | /** 26 | * Reverse the migrations. 27 | * 28 | * @return void 29 | */ 30 | public function down() 31 | { 32 | Schema::dropIfExists('users'); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Develop REST API with Lumen and JWT authentication 2 | 3 | # Installation 4 | 5 | 1. Clone this repo 6 | 7 | ``` 8 | git clone https://github.com/samironbarai/lumen-rest-api-jwt-auth.git 9 | ``` 10 | 11 | 2. Install composer packages 12 | 13 | ``` 14 | cd lumen-rest-api-jwt-auth 15 | $ composer install 16 | ``` 17 | 18 | 3. Create and setup .env file 19 | 20 | ``` 21 | make a copy of .env.example 22 | $ copy .env.example .env 23 | $ php artisan key:generate 24 | put database credentials in .env file 25 | $ php artisan jwt:secret 26 | ``` 27 | 28 | 4. Migrate and insert records 29 | 30 | ``` 31 | $ php artisan migrate 32 | ``` 33 | 34 | To test application follow the tutorial bellow. 35 | Click on the image bellow to see YouTube video. 36 | 37 | [![Lumen REST API Crash Course 2021 (Passport and JWT authentication)](https://img.youtube.com/vi/qG0djDRXV_g/0.jpg)](https://www.youtube.com/watch?v=qG0djDRXV_g) 38 | 39 | Please visit my website. 40 | [samironbarai.com](https://samironbarai.com/) 41 | -------------------------------------------------------------------------------- /public/index.php: -------------------------------------------------------------------------------- 1 | run(); 29 | -------------------------------------------------------------------------------- /app/Http/Middleware/Authenticate.php: -------------------------------------------------------------------------------- 1 | auth = $auth; 26 | } 27 | 28 | /** 29 | * Handle an incoming request. 30 | * 31 | * @param \Illuminate\Http\Request $request 32 | * @param \Closure $next 33 | * @param string|null $guard 34 | * @return mixed 35 | */ 36 | public function handle($request, Closure $next, $guard = null) 37 | { 38 | if ($this->auth->guard($guard)->guest()) { 39 | return response('Unauthorized.', 401); 40 | } 41 | 42 | return $next($request); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /app/Providers/AuthServiceProvider.php: -------------------------------------------------------------------------------- 1 | app['auth']->viaRequest('api', function ($request) { 34 | if ($request->input('api_token')) { 35 | return User::where('api_token', $request->input('api_token'))->first(); 36 | } 37 | }); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /routes/web.php: -------------------------------------------------------------------------------- 1 | get('/', function () use ($router) { 17 | return $router->app->version(); 18 | }); 19 | 20 | 21 | $router->group(['prefix' => 'api'], function () use ($router) { 22 | $router->post('/register', 'AuthController@register'); 23 | $router->post('/login', 'AuthController@login'); 24 | 25 | $router->group(['middleware' => 'auth'], function () use ($router) { 26 | $router->post('/logout', 'AuthController@logout'); 27 | $router->get('/posts', 'PostController@index'); 28 | $router->post('/posts', 'PostController@store'); 29 | $router->put('/posts/{id}', 'PostController@update'); 30 | $router->delete('/posts/{id}', 'PostController@destroy'); 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /artisan: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | make( 32 | 'Illuminate\Contracts\Console\Kernel' 33 | ); 34 | 35 | exit($kernel->handle(new ArgvInput, new ConsoleOutput)); 36 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "laravel/lumen", 3 | "description": "The Laravel Lumen Framework.", 4 | "keywords": ["framework", "laravel", "lumen"], 5 | "license": "MIT", 6 | "type": "project", 7 | "require": { 8 | "php": "^7.3|^8.0", 9 | "flipbox/lumen-generator": "^8.2", 10 | "laravel/lumen-framework": "^8.0", 11 | "tymon/jwt-auth": "^1.0" 12 | }, 13 | "require-dev": { 14 | "fakerphp/faker": "^1.9.1", 15 | "mockery/mockery": "^1.3.1", 16 | "phpunit/phpunit": "^9.3" 17 | }, 18 | "autoload": { 19 | "psr-4": { 20 | "App\\": "app/", 21 | "Database\\Factories\\": "database/factories/", 22 | "Database\\Seeders\\": "database/seeders/" 23 | } 24 | }, 25 | "autoload-dev": { 26 | "classmap": [ 27 | "tests/" 28 | ] 29 | }, 30 | "config": { 31 | "preferred-install": "dist", 32 | "sort-packages": true, 33 | "optimize-autoloader": true 34 | }, 35 | "minimum-stability": "dev", 36 | "prefer-stable": true, 37 | "scripts": { 38 | "post-root-package-install": [ 39 | "@php -r \"file_exists('.env') || copy('.env.example', '.env');\"" 40 | ] 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /app/Models/User.php: -------------------------------------------------------------------------------- 1 | getKey(); 43 | } 44 | 45 | /** 46 | * Return a key value array, containing any custom claims to be added to the JWT. 47 | * 48 | * @return array 49 | */ 50 | public function getJWTCustomClaims() 51 | { 52 | return []; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /app/Exceptions/Handler.php: -------------------------------------------------------------------------------- 1 | title = $request->title; 20 | $post->body = $request->body; 21 | 22 | if ($post->save()) { 23 | return response()->json(['status' => 'success', 'message' => 'Post created successfully']); 24 | } 25 | } catch (\Exception $e) { 26 | return response()->json(['status' => 'error', 'message' => $e->getMessage()]); 27 | } 28 | } 29 | 30 | public function update(Request $request, $id) 31 | { 32 | try { 33 | $post = Post::findOrFail($id); 34 | $post->title = $request->title; 35 | $post->body = $request->body; 36 | 37 | if ($post->save()) { 38 | return response()->json(['status' => 'success', 'message' => 'Post updated successfully']); 39 | } 40 | } catch (\Exception $e) { 41 | return response()->json(['status' => 'error', 'message' => $e->getMessage()]); 42 | } 43 | } 44 | 45 | public function destroy($id) 46 | { 47 | try { 48 | $post = Post::findOrFail($id); 49 | 50 | if ($post->delete()) { 51 | return response()->json(['status' => 'success', 'message' => 'Post deleted successfully']); 52 | } 53 | } catch (\Exception $e) { 54 | return response()->json(['status' => 'error', 'message' => $e->getMessage()]); 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /app/Http/Controllers/AuthController.php: -------------------------------------------------------------------------------- 1 | name; 13 | $email = $request->email; 14 | $password = $request->password; 15 | 16 | // Check if field is empty 17 | if (empty($name) or empty($email) or empty($password)) { 18 | return response()->json(['status' => 'error', 'message' => 'You must fill all the fields']); 19 | } 20 | 21 | // Check if email is valid 22 | if (!filter_var($email, FILTER_VALIDATE_EMAIL)) { 23 | return response()->json(['status' => 'error', 'message' => 'You must enter a valid email']); 24 | } 25 | 26 | // Check if password is greater than 5 character 27 | if (strlen($password) < 6) { 28 | return response()->json(['status' => 'error', 'message' => 'Password should be min 6 character']); 29 | } 30 | 31 | // Check if user already exist 32 | if (User::where('email', '=', $email)->exists()) { 33 | return response()->json(['status' => 'error', 'message' => 'User already exists with this email']); 34 | } 35 | 36 | // Create new user 37 | try { 38 | $user = new User(); 39 | $user->name = $request->name; 40 | $user->email = $request->email; 41 | $user->password = app('hash')->make($request->password); 42 | 43 | if ($user->save()) { 44 | return $this->login($request); 45 | } 46 | } catch (\Exception $e) { 47 | return response()->json(['status' => 'error', 'message' => $e->getMessage()]); 48 | } 49 | } 50 | 51 | /** 52 | * Log the user out (Invalidate the token). 53 | * 54 | * @return \Illuminate\Http\JsonResponse 55 | */ 56 | public function logout() 57 | { 58 | auth()->logout(); 59 | 60 | return response()->json(['message' => 'Successfully logged out']); 61 | } 62 | 63 | public function login(Request $request) 64 | { 65 | $email = $request->email; 66 | $password = $request->password; 67 | 68 | // Check if field is empty 69 | if (empty($email) or empty($password)) { 70 | return response()->json(['status' => 'error', 'message' => 'You must fill all the fields']); 71 | } 72 | 73 | $credentials = request(['email', 'password']); 74 | 75 | if (!$token = auth()->attempt($credentials)) { 76 | return response()->json(['error' => 'Unauthorized'], 401); 77 | } 78 | 79 | return $this->respondWithToken($token); 80 | } 81 | 82 | /** 83 | * Get the token array structure. 84 | * 85 | * @param string $token 86 | * 87 | * @return \Illuminate\Http\JsonResponse 88 | */ 89 | protected function respondWithToken($token) 90 | { 91 | return response()->json([ 92 | 'access_token' => $token, 93 | 'token_type' => 'bearer', 94 | 'expires_in' => auth()->factory()->getTTL() * 60 95 | ]); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /bootstrap/app.php: -------------------------------------------------------------------------------- 1 | bootstrap(); 8 | 9 | date_default_timezone_set(env('APP_TIMEZONE', 'UTC')); 10 | 11 | /* 12 | |-------------------------------------------------------------------------- 13 | | Create The Application 14 | |-------------------------------------------------------------------------- 15 | | 16 | | Here we will load the environment and create the application instance 17 | | that serves as the central piece of this framework. We'll use this 18 | | application as an "IoC" container and router for this framework. 19 | | 20 | */ 21 | 22 | $app = new Laravel\Lumen\Application( 23 | dirname(__DIR__) 24 | ); 25 | 26 | // $app->withFacades(); 27 | 28 | $app->withEloquent(); 29 | 30 | /* 31 | |-------------------------------------------------------------------------- 32 | | Register Container Bindings 33 | |-------------------------------------------------------------------------- 34 | | 35 | | Now we will register a few bindings in the service container. We will 36 | | register the exception handler and the console kernel. You may add 37 | | your own bindings here if you like or you can make another file. 38 | | 39 | */ 40 | 41 | $app->singleton( 42 | Illuminate\Contracts\Debug\ExceptionHandler::class, 43 | App\Exceptions\Handler::class 44 | ); 45 | 46 | $app->singleton( 47 | Illuminate\Contracts\Console\Kernel::class, 48 | App\Console\Kernel::class 49 | ); 50 | 51 | /* 52 | |-------------------------------------------------------------------------- 53 | | Register Config Files 54 | |-------------------------------------------------------------------------- 55 | | 56 | | Now we will register the "app" configuration file. If the file exists in 57 | | your configuration directory it will be loaded; otherwise, we'll load 58 | | the default version. You may register other files below as needed. 59 | | 60 | */ 61 | 62 | $app->configure('auth'); 63 | 64 | /* 65 | |-------------------------------------------------------------------------- 66 | | Register Middleware 67 | |-------------------------------------------------------------------------- 68 | | 69 | | Next, we will register the middleware with the application. These can 70 | | be global middleware that run before and after each request into a 71 | | route or middleware that'll be assigned to some specific routes. 72 | | 73 | */ 74 | 75 | // $app->middleware([ 76 | // App\Http\Middleware\ExampleMiddleware::class 77 | // ]); 78 | 79 | $app->routeMiddleware([ 80 | 'auth' => App\Http\Middleware\Authenticate::class, 81 | ]); 82 | 83 | /* 84 | |-------------------------------------------------------------------------- 85 | | Register Service Providers 86 | |-------------------------------------------------------------------------- 87 | | 88 | | Here we will register all of the application's service providers which 89 | | are used to bind services into the container. Service providers are 90 | | totally optional, so you are not required to uncomment this line. 91 | | 92 | */ 93 | 94 | // $app->register(App\Providers\AppServiceProvider::class); 95 | $app->register(App\Providers\AuthServiceProvider::class); 96 | // $app->register(App\Providers\EventServiceProvider::class); 97 | $app->register(Flipbox\LumenGenerator\LumenGeneratorServiceProvider::class); 98 | $app->register(Tymon\JWTAuth\Providers\LumenServiceProvider::class); 99 | 100 | /* 101 | |-------------------------------------------------------------------------- 102 | | Load The Application Routes 103 | |-------------------------------------------------------------------------- 104 | | 105 | | Next we will include the routes file so that they can all be added to 106 | | the application. This will provide all of the URLs the application 107 | | can respond to, as well as the controllers that may handle them. 108 | | 109 | */ 110 | 111 | $app->router->group([ 112 | 'namespace' => 'App\Http\Controllers', 113 | ], function ($router) { 114 | require __DIR__ . '/../routes/web.php'; 115 | }); 116 | 117 | return $app; 118 | --------------------------------------------------------------------------------