composer require darkaonline/l5-swagger
composer require tymon/jwt-auth
tymon/jwt-auth
package, we need to publish the configuration file by running the following command in the terminal which will create a file config/jwt.php
.php artisan vendor:publish --provider="Tymon\JWTAuth\Providers\LaravelServiceProvider"
php artisan jwt:secret
darkaonline/l5-swagger
is needed to run the following command:php artisan vendor:publish --provider "L5Swagger\L5SwaggerServiceProvider"
/**
*
* @OA\Info(
* title="JGomes site API",
* version="1.0.0",
* ),
* @OA\SecurityScheme(
* type="http",
* description="Login with email and password to get the authentication token",
* name="Token based on user credentials",
* in="header",
* scheme="bearer",
* bearerFormat="JWT",
* securityScheme="apiAuth",
* )
*/
This will activate the authorize button, like this:
/**
* @OA\Post(
* path="/api/send",
* summary="Send a message",
* tags={"Message"},
* @OA\Parameter(
* name="name",
* in="query",
* description="User's name",
* required=true,
* @OA\Schema(type="string")
* ),
* @OA\Parameter(
* name="email",
* in="query",
* description="User's email",
* required=true,
* @OA\Schema(type="string")
* ),
* @OA\Parameter(
* name="subject",
* in="query",
* description="User's subject",
* required=false,
* @OA\Schema(type="string")
),
* @OA\Parameter(
* name="content",
* in="query",
* description="User's content",
* required=true,
* @OA\Schema(type="string")
* ),
* @OA\Response(response="201", description="Message sent successfully"),
* @OA\Response(response="422", description="Validation errors")
* )
*/
For this example there's no need to have a valid token:
/**
* @OA\Post(
* path="/api/v1/login",
* summary="Sign in via api",
* description="Login by username email and password",
* operationId="authLoginApi",
* tags={"Authorization"},
* @OA\RequestBody(
* required=true,
* description="User authentication",
* @OA\JsonContent(
* required={"email","password"},
* @OA\Property(property="email", type="string", format="text", example="test@test.test"),
* @OA\Property(property="password", type="string", format="text", example="Test@123"),
* ),
* ),
* @OA\Response(
* response=422,
* description="Wrong credentials response - Password is invalid",
* @OA\JsonContent(
* @OA\Property(property="message", type="string", example="Sorry, wrong password. Please try again")
* )
* )
* )
*/
When the login is done with success, we receive a valid token, like this:
After, we need to copy the token received in the last request, and create an authorization by clicking on the button authorize to paste the token there:
/**
* @OA\Get(
* path="/api/v1/check",
* summary="Check if user is authenticated",
* security={{ "apiAuth": {} }},
* description="Check if user is authenticated",
* operationId="CheckIfUserIsAuthenticated",
* tags={"Authorization"},
* @OA\Response(
* response=401,
* description="Not authenticated",
* @OA\JsonContent(
* @OA\Property(property="message", type="string", example="Need to the login first.")
* )
* )
* )
* )
*/
Authenticated result with a valid token:
Do logout:
Unauthenticated result:
As the Swagger template is not suppose to be editable, it is very hard to do optimizations on the UI.
Touching on the template in vendor is a big NO NO.
To solve this let's create a copy of the template and paste it in the resources dir like this:
The reason why this project needs some customization is because it is served by a proxy reverse, so the URL keeps with the local IP ( 127.0.0.1 even in prod ) instead the domain, which I don't like.
In order to put the correct url in place, it was needed to define the following vars containing the correct URL according the environment in ' app -> Providers -> AppServiceProvider.php ', like this:
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
/**
* Register any application services.
*
* @return void
*/
public function register(): void
{
app()->singleton('urlToDocs', function () {
return env('APP_URL') . '/docs/api-docs.json';
});
app()->singleton('swaggeruibundle', function () {
return env('APP_URL') . '/docs/asset/swagger-ui-bundle.js';
});
app()->singleton('swaggeruistandalonepreset', function () {
return env('APP_URL') . '/docs/asset/swagger-ui-standalone-preset.js';
});
app()->singleton('swagger-ui', function () {
return env('APP_URL') . '/docs/asset/swagger-ui.css';
});
}
public function boot()
{
//
}
}
This config is located here:
And let's use this vars ' urlToDocs ', ' swaggeruibundle ', ' swaggeruistandalonepreset ', ' swagger-ui ' in the following customized view:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>{{config('l5-swagger.documentations.'.$documentation.'.api.title')}}</title>
<link rel="stylesheet" type="text/css" href="{!! app('swagger-ui') !!}"> <--------- HERE
<style>
html
{
box-sizing: border-box;
overflow: -moz-scrollbars-vertical;
overflow-y: scroll;
}
*,
*:before,
*:after
{
box-sizing: inherit;
}
body {
margin:0;
background: #fafafa;
}
</style>
</head>
<body>
<div id="swagger-ui"></div>
<script src="{!! app('swaggeruibundle') !!}"></script> <--------- HERE
<script src="{!! app('swaggeruistandalonepreset') !!}"></script> <--------- HERE
<script>
window.onload = function() {
// Build a system
const ui = SwaggerUIBundle({
dom_id: '#swagger-ui',
url: "{!! app('urlToDocs') !!}", <--------- HERE
operationsSorter: {!! isset($operationsSorter) ? '"' . $operationsSorter . '"' : 'null' !!},
configUrl: {!! isset($configUrl) ? '"' . $configUrl . '"' : 'null' !!},
validatorUrl: {!! isset($validatorUrl) ? '"' . $validatorUrl . '"' : 'null' !!},
oauth2RedirectUrl: "{{ route('l5-swagger.'.$documentation.'.oauth2_callback', [], $useAbsolutePath) }}",
requestInterceptor: function(request) {
request.headers['X-CSRF-TOKEN'] = '{{ csrf_token() }}';
return request;
},
presets: [
SwaggerUIBundle.presets.apis,
SwaggerUIStandalonePreset
],
plugins: [
SwaggerUIBundle.plugins.DownloadUrl
],
layout: "StandaloneLayout",
docExpansion : "{!! config('l5-swagger.defaults.ui.display.doc_expansion', 'none') !!}",
deepLinking: true,
filter: {!! config('l5-swagger.defaults.ui.display.filter') ? 'true' : 'false' !!},
persistAuthorization: "{!! config('l5-swagger.defaults.ui.authorization.persist_authorization') ? 'true' : 'false' !!}",
})
window.ui = ui
@if(in_array('oauth2', array_column(config('l5-swagger.defaults.securityDefinitions.securitySchemes'), 'type')))
ui.initOAuth({
usePkceWithAuthorizationCodeGrant: "{!! (bool)config('l5-swagger.defaults.ui.authorization.oauth2.use_pkce_with_authorization_code_grant') !!}"
})
@endif
}
</script>
</body>
</html>
return [
'default' => 'default',
'documentations' => [
'default' => [
'api' => [
'title' => 'L5 Swagger UI',
],
.
.
.
.
],
],
'defaults' => [
'routes' => [
'docs' => 'docs',
'oauth2_callback' => 'api/oauth2-callback',
'middleware' => [
'api' => [],
'asset' => [],
'docs' => [],
'oauth2_callback' => [],
],
'group_options' => [],
],
'paths' => [
'docs' => storage_path('api-docs'),
'views' => base_path('resources/views/vendor/l5-swagger'), <----------- HERE
'base' => env('L5_SWAGGER_BASE_PATH', null),
'swagger_ui_assets_path' => env('L5_SWAGGER_UI_ASSETS_PATH', 'vendor/swagger-api/swagger-ui/dist/'),
'excludes' => [],
],
.
.
.
.
],
],
];
<?php
Route::prefix('v1')->group(function () {
.
.
.
.
// Protected routes by Sanctum
Route::middleware('auth:sanctum')->group(function ()
{
.
.
. ( For the routes defined here under the middleware 'auth:sanctum', the Swagger returns a 405 )
.
.
});
// Middleware de fallback to return 401 for unauthenticated requests
Route::fallback(function () {
return response()->json(['error' => 'Unauthorized'], 401); <------- To solve this problem just add this HERE
});
});
php artisan l5-swagger:generate