loading...
Cover image for Laravel Api Documentation with Swagger and Passport

Laravel Api Documentation with Swagger and Passport

avsecdongol profile image Abishek Dongol ・11 min read

First, let’s see what we are going to have in this article:

  1. Brief introduction about Swagger and its Importance.
  2. Install and configure Laravel with Passport.
  3. Integration of Swagger in Laravel Application.
  4. How to use Swagger generated documentation.

What is Swagger?

As per Swagger: swagger is the most popular, and widely used tool for implementing the OpenAPI specification. The swagger consists of a toolset that includes a mix of open-source, free, and commercial tools while using at different stages of the API lifecycle.

Important for Implementing OpenAPI/Swagger

There are many reasons for implementing an OpenApi specification between frontend and backend can be valuable.

1. Consistency and easy to collaborate on API Design

From initial to the end of the project several developers, parties, and clients will be involved. So, to make API design flexible and consistent we need to follow certain rules and structures which make others understand and collaborate easily.

2. Save Time and Avoid Errors When Writing Code

While building software we need to count on all aspects which can affect the deadline of the project. One of the major aspects that need to be mentioned and managed to build applications is nonother then time. So, swagger is one of the most powerful tools which helps to manage time and reduce the problem of getting errors because of its code-generators while developing APIs.

3. Generate Beautiful and Interactive Documentation

Besides consistency, collaborative and time saving good documentation are one of the crucial parts of every API because it helps to understand the business logic of API and method of calling it for the developer who is involved while using API. Swagger provides a set of great tools like swagger editor, swagger codegen, Swagger UI, Swagger inspector for designing APIs. Among them, Swagger UI is the first tool that interacts with clients and developers which displays a list of available API operations which makes it easy to send a request in their browser with required parameters to test and get responses before writing code.

4. Distribution of API in the market

OpenAPI/Swagger covers all the required things to build a better and attractive application. But there is another best benefit of using it which is distribution/publishing of OpenAPI which allows client developers to use their preferred tools to combine and to use with the base API.

Before moving on to integration of swagger let’s take a look at the result of the documentation page that we are going to generate.

Alt Text

Install and configure Laravel with Passport

  • Let’s create our new Laravel application using the following mentioned command

    composer create-project --prefer-dist laravel/laravel blog

  • created a database and then update the values of the following variables within the .env file:

    DB_DATABASE
    DB_USERNAME
    DB_PASSWORD

  • Install Laravel Passport using composer

    composer require laravel/passport

  • Run the following command to migrate your database

    php artisan migrate

  • Next, to create the encryption keys needed to generate secured access tokens and save at secure place, run the command below

    php artisan passport:install

  • Immediately after the installation process from the preceding command is finished, add the Laravel\Passport\HasApiTokens trait to your App\User model as shown here

// app/User.php
<?php
namespace App;
...
use Laravel\Passport\HasApiTokens; // include this
class User extends Authenticatable
{
    use Notifiable, HasApiTokens; // update this line
    ...
}

  • open the app/Providers/AuthServiceProvider file and update its content as shown below
// app/Providers/AuthServiceProvider.php

<?php
namespace App\Providers;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Gate;
use Laravel\Passport\Passport; // add this

class AuthServiceProvider extends ServiceProvider
{
    /**
     * The policy mappings for the application.
     *
     * @var array
     */
    protected $policies = [
         'App\Model' => 'App\Policies\ModelPolicy', // uncomment this line
    ];

    /**
     * Register any authentication / authorization services.
     *
     * @return void
     */
    public function boot()
    {
        $this->registerPolicies();
        Passport::routes(); // Add this
    }
}

  • to authenticate any incoming API requests, open the config/auth configuration file and set the driver option of the API authentication guard to passport
// config/auth

<?php

return [
    ...

    'guards' => [
        'web' => [
            'driver' => 'session',
            'provider' => 'users',
        ],

        'api' => [
            'driver' => 'passport', // set this to passport
            'provider' => 'users',
            'hash' => false,
        ],
    ],

    ...
];

Integration of Swagger in Laravel Application

  • Now install the Swagger according to the Laravel version that you have installed. For more information please feel free to visit DarkaOnLine/L5-Swagger

    composer require "darkaonline/l5-swagger"

  • You can publish Swagger’s configuration and view files into your project by running the following command

    php artisan vendor:publish --provider "L5Swagger\L5SwaggerServiceProvider"

  • Now we have config/l5-swagger.php file with huge amount of options in which we need to add middleware in api array as below.

'api' => [
        /*
        |--------------------------------------------------------------------------
        | Edit to set the api's title
        |--------------------------------------------------------------------------
        */
        \App\Http\Middleware\EncryptCookies::class,
        \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
        \Illuminate\Session\Middleware\StartSession::class,
        \Illuminate\View\Middleware\ShareErrorsFromSession::class,
        \App\Http\Middleware\VerifyCsrfToken::class,
        \Illuminate\Routing\Middleware\SubstituteBindings::class,
        \Laravel\Passport\Http\Middleware\CreateFreshApiToken::class,
        'auth',
        'title' => 'Integration Swagger in Laravel with Passport Auth',
    ],

  • Again, to enable passport authentication we need to uncomment Open API 3.0 support in security array of config/l5-swagger.php file
'security' => [

        // Open API 3.0 support
        'passport' => [ // Unique name of security
            'type' => 'oauth2', // The type of the security scheme. Valid values are "basic", "apiKey" or "oauth2".
            'description' => 'Laravel passport oauth2 security.',
            'in' => 'header',
            'scheme' => 'https',
            'flows' => [
                "password" => [
                    "authorizationUrl" => config('app.url') . '/oauth/authorize',
                    "tokenUrl" => config('app.url') . '/oauth/token',
                    "refreshUrl" => config('app.url') . '/token/refresh',
                    "scopes" => []
                ],
            ],
        ],
    ],

  • Before dive into deep lets get general information about Swagger Annotations

    • @OA() refers to what kind of HTTP method we’ll use. The method can be GET, POST, PUT or DELETE.

    • @OA\Parameter refers to the name of the parameters that will pass to API.

    • @OA\Response() which kind of response and code will we return if the data is correct or incorrect.

    • The magic parameter is required to include for passport authorization inside controller of specific method comment

     // magic parameter
     *security={
     *{
     *"passport": {}},
     *},
  • Now, we need to create routes for API. So, here’s routes/api
<?php

use Illuminate\Http\Request;

/*
|--------------------------------------------------------------------------
| API Routes
|--------------------------------------------------------------------------
|
| Here is where you can register API routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| is assigned the "api" middleware group. Enjoy building your API!
|
*/



Route::group([
    'prefix' => 'v1',
    'as' => 'api.',
    'middleware' => ['auth:api']
], function () {
    //lists all users
    Route::post('/all-user', 'API\ V1\ApiController@allUsers')->name('all-user');
});

//auth routes
Route::post('v1/user-register', 'API\ V1\AuthController@register');
Route::post('v1/user-login', 'API\ V1\AuthController@login');

//lists all active tests
Route::post('v1/test', 'API\ V1\ApiController@getActiveTest');

//lists payment detail of an individual
Route::post('v1/paymentdetail/{id}', 'API\ V1\API@paymentDetail');

//lists test user details
Route::post('v1/testuser', 'API\ V1\ApiController@testUsers');

//lists all active packages
Route::post('v1/package', 'API\ V1\ApiController@getPackages');

//lists every tests within the package
Route::post('v1/packagedetail/{id}', 'API\ V1\ApiController@getPackageDetails');

//lists all test details(test sections, questions, options)
Route::post('v1/testdetail/{id}', 'API\ V1\ApiController@testDetails');

  • At first, we need to add few Swagger Annotations in Controller.php which is located at app/Http/Controllers/Controller.php
<?php

namespace App\Http\Controllers;

use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Foundation\Validation\ValidatesRequests;
use Illuminate\Routing\Controller as BaseController;

class Controller extends BaseController
{
    /**
     * @OA\Info(
     *      version="1.0.0",
     *      title="Integration Swagger in Laravel with Passport Auth Documentation",
     *      description="Implementation of Swagger with in Laravel",
     *      @OA\Contact(
     *          email="admin@admin.com"
     *      ),
     *      @OA\License(
     *          name="Apache 2.0",
     *          url="http://www.apache.org/licenses/LICENSE-2.0.html"
     *      )
     * )
     *
     * @OA\Server(
     *      url=L5_SWAGGER_CONST_HOST,
     *      description="Demo API Server"
     * )

     *
     *
     */
    use AuthorizesRequests, DispatchesJobs, ValidatesRequests;
}

  • Now create two controller named ApiController and AuthController inside app/Http/Controllers/Api/V1 directory. In Laravel controller can be created via the following the command

    php artisan make:controller Api/V1/ApiController

    php artisan make:controller Api/V1/AuthController

  • Here is my code of AuthController

<?php

namespace App\Http\Controllers\API\Mobile\V1;

use App\Http\Controllers\Controller;
use App\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Validator;
use Symfony\Component\HttpFoundation\Response;

class AuthController extends Controller
{

    /**
     * @OA\Post(
     ** path="/v1/user-login",
     *   tags={"Login"},
     *   summary="Login",
     *   operationId="login",
     *
     *   @OA\Parameter(
     *      name="email",
     *      in="query",
     *      required=true,
     *      @OA\Schema(
     *           type="string"
     *      )
     *   ),
     *   @OA\Parameter(
     *      name="password",
     *      in="query",
     *      required=true,
     *      @OA\Schema(
     *          type="string"
     *      )
     *   ),
     *   @OA\Response(
     *      response=200,
     *       description="Success",
     *      @OA\MediaType(
     *           mediaType="application/json",
     *      )
     *   ),
     *   @OA\Response(
     *      response=401,
     *       description="Unauthenticated"
     *   ),
     *   @OA\Response(
     *      response=400,
     *      description="Bad Request"
     *   ),
     *   @OA\Response(
     *      response=404,
     *      description="not found"
     *   ),
     *      @OA\Response(
     *          response=403,
     *          description="Forbidden"
     *      )
     *)
     **/
    /**
     * login api
     *
     * @return \Illuminate\Http\Response
     */
    public function login(Request $request)
    {
        $validator = $request->validate([
            'email' => 'email|required',
            'password' => 'required'
        ]);


        if (!auth()->attempt($validator)) {
            return response()->json(['error' => 'Unauthorised'], 401);
        } else {
            $success['token'] = auth()->user()->createToken('authToken')->accessToken;
            $success['user'] = auth()->user();
            return response()->json(['success' => $success])->setStatusCode(Response::HTTP_ACCEPTED);
        }
    }

    /**
     * @OA\Post(
     ** path="/v1/user-register",
     *   tags={"Register"},
     *   summary="Register",
     *   operationId="register",
     *
     *  @OA\Parameter(
     *      name="name",
     *      in="query",
     *      required=true,
     *      @OA\Schema(
     *           type="string"
     *      )
     *   ),
     *  @OA\Parameter(
     *      name="email",
     *      in="query",
     *      required=true,
     *      @OA\Schema(
     *           type="string"
     *      )
     *   ),
     *   @OA\Parameter(
     *       name="mobile_number",
     *      in="query",
     *      required=true,
     *      @OA\Schema(
     *           type="integer"
     *      )
     *   ),
     *   @OA\Parameter(
     *      name="password",
     *      in="query",
     *      required=true,
     *      @OA\Schema(
     *           type="string"
     *      )
     *   ),
     *      @OA\Parameter(
     *      name="password_confirmation",
     *      in="query",
     *      required=true,
     *      @OA\Schema(
     *           type="string"
     *      )
     *   ),
     *   @OA\Response(
     *      response=201,
     *       description="Success",
     *      @OA\MediaType(
     *           mediaType="application/json",
     *      )
     *   ),
     *   @OA\Response(
     *      response=401,
     *       description="Unauthenticated"
     *   ),
     *   @OA\Response(
     *      response=400,
     *      description="Bad Request"
     *   ),
     *   @OA\Response(
     *      response=404,
     *      description="not found"
     *   ),
     *      @OA\Response(
     *          response=403,
     *          description="Forbidden"
     *      )
     *)
     **/
    /**
     * Register api
     *
     * @return \Illuminate\Http\Response
     */
    public function register(Request $request)
    {
        $validator = Validator::make($request->all(), [
            'name' => 'required',
            'email' => 'required|email|unique:users',
            'password' => 'required|confirmed',
            'mobile_number' => 'required|unique:users'
        ]);

        if ($validator->fails()) {
            return response()->json(['error' => $validator->errors()], 401);
        }

        $input = $request->all();
        $input['password'] = Hash::make($input['password']);
        $user = User::create($input);
        $success['token'] =  $user->createToken('authToken')->accessToken;
        $success['name'] =  $user->name;
        return response()->json(['success' => $success])->setStatusCode(Response::HTTP_CREATED);
    }
    /**
     * details api
     *
     * @return \Illuminate\Http\Response
     */
    public function details()
    {
        $user = Auth::user();
        return response()->json(['success' => $user], $this->successStatus);
    }
}

  • Here is my code of ApiController with related Resource collection but you can use your own function logic
<?php

namespace App\Http\Controllers\API\Mobile\V1;

use App\Http\Controllers\Controller;
use App\Package;
use App\Payment;
use App\Test;
use App\User;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use App\Http\Resources\User as UserResource;
use App\Http\Resources\ActiveTest as ActiveTestResource;
use App\Http\Resources\Test as TestResource;
use App\Http\Resources\Payment as PaymentResource;
use App\Http\Resources\PackageDetail as PackageDetailResource;
use App\Http\Resources\Package as PackageResource;


use App\Http\Resources\UserTest as UserTestResource;

class ApiController extends Controller
{


    /**
     * @OA\Post(
     *      path="/v1/all-user",
     *      operationId="getUserList",
     *      tags={"Users"},
     * security={
     *  {"passport": {}},
     *   },
     *      summary="Get list of users",
     *      description="Returns list of users",
     *      @OA\Response(
     *          response=200,
     *          description="Successful operation",
     *          @OA\MediaType(
     *           mediaType="application/json",
     *      )
     *      ),
     *      @OA\Response(
     *          response=401,
     *          description="Unauthenticated",
     *      ),
     *      @OA\Response(
     *          response=403,
     *          description="Forbidden"
     *      ),
     * @OA\Response(
     *      response=400,
     *      description="Bad Request"
     *   ),
     * @OA\Response(
     *      response=404,
     *      description="not found"
     *   ),
     *  )
     */
    public function allUsers()
    {
        $users = User::all();
        return response()->json([
            'status' => 'success',
            'status_code' => Response::HTTP_OK,
            'data' => [
                'users' => UserResource::collection($users)
            ],

            'message' => 'All users pulled out successfully'

        ]);
    }

    /**
     * @OA\Post(
     *      path="/v1/test",
     *      operationId="getActiveTestList",
     *      tags={"Tests"},

     *      summary="Get list of active tests",
     *      description="Returns list of active tests",
     *      @OA\Response(
     *          response=200,
     *          description="Successful operation",
     *          @OA\MediaType(
     *           mediaType="application/json",
     *      )
     *      ),
     *      @OA\Response(
     *          response=401,
     *          description="Unauthenticated",
     *      ),
     *      @OA\Response(
     *          response=403,
     *          description="Forbidden"
     *      ),
     * @OA\Response(
     *      response=400,
     *      description="Bad Request"
     *   ),
     * @OA\Response(
     *      response=404,
     *      description="not found"
     *   ),
     *  )
     */
    public function getActiveTest()
    {
        $tests = Test::where('status', 1)->get();
        return response()->json([
            'status' => 'success',
            'status_code' => Response::HTTP_OK,
            'data' => [
                'test' => ActiveTestResource::collection($tests)
            ],

            'message' => 'Active tests pulled out successfully'
        ]);
    }

    /**
     * @OA\Post(
     ** path="/v1/testdetail/{id}",
     *   tags={"Tests"},
     *   summary="Test Detail",
     *   operationId="testdetails",
     *
     *   @OA\Parameter(
     *      name="id",
     *      in="path",
     *      required=true,
     *      @OA\Schema(
     *           type="integer"
     *      )
     *   ),
     *
     *   @OA\Response(
     *      response=200,
     *       description="Success",
     *      @OA\MediaType(
     *           mediaType="application/json",
     *      )
     *   ),
     *   @OA\Response(
     *      response=401,
     *      description="Unauthenticated"
     *   ),
     *   @OA\Response(
     *      response=400,
     *      description="Bad Request"
     *   ),
     *   @OA\Response(
     *      response=404,
     *      description="not found"
     *   ),
     *      @OA\Response(
     *          response=403,
     *          description="Forbidden"
     *      )
     *)
     **/
    public function testDetails($id)
    {
        $test = Test::with('testSections', 'testSections.questions')->findOrFail($id);
        return response()->json([
            'status' => 'success',
            'status_code' => Response::HTTP_OK,
            'data' => [
                'test' => new TestResource($test)
            ],

            'message' => 'Test detail pulled out successfully'
        ]);
    }

    /**
     * @OA\Post(
     ** path="/v1/paymentdetail/{id}",
     *   tags={"Payments"},
     *   summary="Payment Detail",
     *   operationId="paymentdetails",
     *
     *   @OA\Parameter(
     *      name="id",
     *      in="path",
     *      required=true,
     *      @OA\Schema(
     *           type="integer"
     *      )
     *   ),
     *
     *   @OA\Response(
     *      response=200,
     *       description="Success",
     *      @OA\MediaType(
     *          mediaType="application/json",
     *      )
     *   ),
     *   @OA\Response(
     *      response=401,
     *       description="Unauthenticated"
     *   ),
     *   @OA\Response(
     *      response=400,
     *      description="Bad Request"
     *   ),
     *   @OA\Response(
     *      response=404,
     *      description="not found"
     *   ),
     *      @OA\Response(
     *          response=403,
     *          description="Forbidden"
     *      )
     *)
     **/
    public function paymentDetail($id)
    {
        $paymentDetail = Payment::where('user_id', $id)->get();
        return response()->json([
            'status' => 'success',
            'status_code' => Response::HTTP_OK,
            'data' => [
                'paymentDetail' => PaymentResource::collection($paymentDetail)
            ],

            'message' => 'Payment detail of the user pulled out successfully'
        ]);
    }

    /**
     * @OA\Post(
     *      path="/v1/package",
     *      operationId="getPackageList",
     *      tags={"Packages"},

     *      summary="Get list of Packages",
     *      description="Returns list of packages",
     *      @OA\Response(
     *          response=200,
     *          description="Successful operation",
     *          @OA\MediaType(
     *           mediaType="application/json",
     *      )
     *      ),
     *      @OA\Response(
     *          response=401,
     *          description="Unauthenticated",
     *      ),
     *      @OA\Response(
     *          response=403,
     *          description="Forbidden"
     *      ),
     * @OA\Response(
     *      response=400,
     *      description="Bad Request"
     *   ),
     * @OA\Response(
     *      response=404,
     *      description="not found"
     *   ),
     *  )
     */
    public function getPackages()
    {
        $packages = Package::where('status', 1)->get();
        return response()->json([
            'status' => 'success',
            'status_code' => Response::HTTP_OK,
            'data' => [
                'active packages' => PackageResource::collection($packages)
            ],

            'message' => 'All active packages pulled out successfully'
        ]);
    }

    /**
     * @OA\Post(
     *      path="/v1/packagedetail/{id}",
     *      operationId="getPackageDetail",
     *      tags={"Packages"},
     *      summary="Get list of Packages detail",
     *      description="Returns list of packages detail",
     *  @OA\Parameter(
     *      name="id",
     *      in="path",
     *      required=true,
     *      @OA\Schema(
     *           type="integer"
     *      )
     *   ),
     *      @OA\Response(
     *          response=200,
     *          description="Successful operation",
     *           @OA\MediaType(
     *           mediaType="application/json",
     *      )
     *      ),
     *      @OA\Response(
     *          response=401,
     *          description="Unauthenticated",
     *      ),
     *      @OA\Response(
     *          response=403,
     *          description="Forbidden"
     *      ),
     * @OA\Response(
     *      response=400,
     *      description="Bad Request"
     *   ),
     * @OA\Response(
     *      response=404,
     *      description="not found"
     *   ),
     *  )
     */
    public function getPackageDetails(Request $request, $id)
    {
        $package = Package::findOrFail($id);
        return response()->json([
            'status' => 'success',
            'status_code' => Response::HTTP_OK,
            'data' => [
                'test package detail' => new PackageDetailResource($package)
            ],

            'message' => 'Test list within the package pulled out successfully'
        ]);
    }

    /**
     * @OA\Post(
     *      path="/v1/testuser",
     *      operationId="getTestUserList",
     *      tags={"Users"},

     *      summary="Get list of Test users",
     *      description="Returns list of test Users",
     *      @OA\Response(
     *          response=200,
     *          description="Successful operation",
     *          @OA\MediaType(
     *           mediaType="application/json",
     *      )
     *      ),
     *      @OA\Response(
     *          response=401,
     *          description="Unauthenticated",
     *      ),
     *      @OA\Response(
     *          response=403,
     *          description="Forbidden"
     *      ),
     * @OA\Response(
     *      response=400,
     *      description="Bad Request"
     *   ),
     * @OA\Response(
     *      response=404,
     *      description="not found"
     *   ),
     *  )
     */
    public function testUsers()
    {
        $user = User::findOrFail(3);

        return response()->json([
            'status' => 'success',
            'status_code' => Response::HTTP_OK,
            'data' => [
                'test' => UserTestResource::collection($user->testUser)
            ],

            'message' => 'Test users\' detail pulled out successfully'
        ]);
    }
}

  • To generate the swagger documentation file just run php artisan l5-swagger: generate command.

  • Now, test all the logic implemented so far by running the application with php artisan serve

  • The generated swagger documentation can be access via entering http:///api/documentation in your project.

  • The generated documentation can look like this.

Alt Text

How to use Swagger generated documentation

  • After run php artisan l5-swagger: generate command we need to enter http:///api/documentation address in our web browser.

Alt Text

  • If we click on any endpoint, it expands with all the parameters we had provided, and even with example response.

  • Let’s try with Login endpoint which does not require authentication.

Alt Text

  • At first, we need to click Try it out button. After that we need to fill the email and password then click the Execute button. If there is not any error then it will return server response as follows:

Alt Text

  • Now let’s try with Get list of users which marked with an unlocked icon under Users endpoints which required authentication.

Alt Text

  • Before accessing authentication endpoint we need to authorize it. So, In order to authorize we need to click the Authorize button.

Alt Text

  • A popup form will be displayed where we need to fill username and password with our project login credentials and need to provide a password grant client key with client_id and client_secret which we had generated using php artisan passport:install command and click Authorize button.

Alt Text

  • And it marks as authorized by displaying the popup.

Alt Text

  • After successfully authorized. Now unlocked icon changed into a locked icon which means we can access that endpoint api.

Alt Text

  • Now we can get all user data by just clicking the try it out button followed by the Execute button.

If you enjoyed this post, I’d be very grateful if you’d help it spread by emailing it to a friend, or sharing it on Twitter or Facebook. Thank you!

Posted on by:

avsecdongol profile

Abishek Dongol

@avsecdongol

Simple and basic mood.

Discussion

markdown guide
 

Thanks for this. Have been using swagger in Symfony. This is a nice introduction into laravel.

I have found some errors in the router

Route::post('v1/user-login', 'API\ V1\AuthController@login');

should be without a space:

Route::post('v1/user-login', 'API\V1\AuthController@login');

Further you introduced the namespace App\Http\Controllers\API\Mobile\V1 but reference to it without the Mobile part.

Lastly, my default laravel doesn't seem to have a field mobile_number in it.

 

It is a good tutorial, but I would add that it is necessary to add the ui for login.
I fixed it after running the following commands and comment this line
'title' => 'Integration Swagger in Laravel with Passport Auth',

composer require laravel/ui

// Generate basic scaffolding...(any of these)
php artisan ui bootstrap
php artisan ui vue
php artisan ui react

// Generate login / registration scaffolding...(any of these)
php artisan ui bootstrap --auth
php artisan ui vue --auth
php artisan ui react --auth

npm install
npm run dev

 

Sweet, you can do for Laravel Sanctum next.

 

why are you sending form data in query parameter in POST request

 

Awesome!
Is there a plugin or an extension i can use to write these annotation quicker with vscode editor?