I would like to restrict my Laravel API from processing parameters as query-string when trying to authenticate the user. I’ve been trying with POSTMAN and all the time I’m able to get the token from my API whether I put the credentials on the body or as query-string in the url.
As per Laravel documentation I think that this is the behavior I want to avoid:
Retrieving Input Via Dynamic Properties
You may also access user input using dynamic properties on the IlluminateHttpRequest instance. For example, if one of your application’s forms contains a name field, you may access the value of the field like so:
$name = $request->name;
When using dynamic properties, Laravel will first look for the parameter’s value in the request payload. If it is not present, Laravel will search for the field in the route parameters.
I’m using Laravel 5.3 and PHP 7.1.0
Here is the POST by using query-string:
Here is the POST by using parameters in the body:
I have configured my CORS by using laravel-cors:
<?php return [ 'defaults' => [ 'supportsCredentials' => false, 'allowedOrigins' => [], 'allowedHeaders' => [], 'allowedMethods' => [], 'exposedHeaders' => [], 'maxAge' => 0, 'hosts' => [], ], 'paths' => [ 'v1/*' => [ 'allowedOrigins' => ['*'], 'allowedHeaders' => ['Accept', 'Content-Type'], 'allowedMethods' => ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'], 'exposedHeaders' => ['Authorization'], 'maxAge' => 3600, ], ], ];
My routes (the relevant ones):
Route::group( [ 'domain' => getenv('API_DOMAIN'), 'middleware' => 'cors', 'prefix' => '/v1', 'namespace' => 'V1' ], function() { /* AUTHENTICATION */ Route::post( 'signin', 'AuthenticationController@signin' )->name('signin'); Route::post( 'signup', 'AuthenticationController@signup' )->name('signup'); } );
When listing my routes php artisan route:list
I get:
------------------------------------------------------------------------------------------------------------------------------------ | Domain | Method | URI | Name | Action | Middleware | | localhost | POST | api/v1/signin | api.signin | AppHttpControllersAPIV1AuthenticationController@signin | api,cors | | localhost | POST | api/v1/signup | api.signup | AppHttpControllersAPIV1AuthenticationController@signup | api,cors | ------------------------------------------------------------------------------------------------------------------------------------
My AuthenticationController
:
<?php namespace AppHttpControllersAPIV1; use IlluminateHttpRequest; use AppHttpControllersController; use TymonJWTAuthExceptionsJWTException; use AppHttpRequests; use JWTAuth; class AuthenticationController extends Controller { public function __construct() { $this->middleware('jwt.auth', ['except' => ['signin', 'signup']]); } public function signin(Request $request) { $credentials = $request->only('email', 'password'); try { if (! $token = JWTAuth::attempt($credentials)) { return response()->json( [ 'error' => 'invalid_credentials' ], 401 ); } } catch (JWTException $e) { return response()->json( [ 'error' => 'could_not_create_token' ], 500 ); } return response()->json(compact('token')); } public function signup(Request $request) { try { $user = User::where(['email' => $request["email"]])->exists(); if($user) { return Response::json( array( 'msg' => "Email {$request->email} already exists" ), 400 ); } $user = new User; $user->create($request->input()); return Response::json( array( 'msg' => 'User signed up.' ) ); } catch (Exception $exception) { return Response::json( array( 'success' => false, 'exception' => $exception ) ); } } }
My Kernel
:
<?php namespace AppHttp; use IlluminateFoundationHttpKernel as HttpKernel; class Kernel extends HttpKernel { /** * The application's global HTTP middleware stack. * * These middleware are run during every request to your application. * * @var array */ protected $middleware = [ IlluminateFoundationHttpMiddlewareCheckForMaintenanceMode::class, BarryvdhCorsHandleCors::class, ]; /** * The application's route middleware groups. * * @var array */ protected $middlewareGroups = [ 'web' => [ AppHttpMiddlewareEncryptCookies::class, IlluminateCookieMiddlewareAddQueuedCookiesToResponse::class, IlluminateSessionMiddlewareStartSession::class, IlluminateViewMiddlewareShareErrorsFromSession::class, AppHttpMiddlewareVerifyCsrfToken::class, IlluminateRoutingMiddlewareSubstituteBindings::class, ], 'api' => [ 'throttle:60,1', 'bindings', ], ]; /** * The application's route middleware. * * These middleware may be assigned to groups or used individually. * * @var array */ protected $routeMiddleware = [ 'auth' => IlluminateAuthMiddlewareAuthenticate::class, 'auth.basic' => IlluminateAuthMiddlewareAuthenticateWithBasicAuth::class, 'bindings' => IlluminateRoutingMiddlewareSubstituteBindings::class, 'can' => IlluminateAuthMiddlewareAuthorize::class, 'guest' => AppHttpMiddlewareRedirectIfAuthenticated::class, 'throttle' => IlluminateRoutingMiddlewareThrottleRequests::class, 'jwt.auth' => TymonJWTAuthMiddlewareGetUserFromToken::class, 'jwt.refresh' => TymonJWTAuthMiddlewareRefreshToken::class, ]; }
And I placed the respective configuration on config/app.php
:
... /* * Package Service Providers... */ BarryvdhLaravelIdeHelperIdeHelperServiceProvider::class, CollectiveHtmlHtmlServiceProvider::class, LaracastsFlashFlashServiceProvider::class, PrettusRepositoryProvidersRepositoryServiceProvider::class, InfyOmGeneratorInfyOmGeneratorServiceProvider::class, InfyOmCoreTemplatesCoreTemplatesServiceProvider::class, /* * Application Service Providers... */ AppProvidersAppServiceProvider::class, // AppProvidersBroadcastServiceProvider::class, AppProvidersAuthServiceProvider::class, AppProvidersEventServiceProvider::class, AppProvidersRouteServiceProvider::class, TymonJWTAuthProvidersJWTAuthServiceProvider::class, BarryvdhCorsServiceProvider::class, AsvaeApiTesterServiceProvider::class, ],
I don’t want to use dingoapi.
I checked these resources:
- JSON Web Token Tutorial: An Example in Laravel and AngularJS
- SERIES: BUILD AN APP WITH LARAVEL5 (BACKEND) AND ANGULARJS (FRONTEND) – PART 1
Last but not least, my composer.json
:
{ "name": "laravel/laravel", "description": "The Laravel Framework.", "keywords": ["framework", "laravel"], "license": "MIT", "type": "project", "require": { "php": "^7.1.0", "laravel/framework": "5.3.*", "barryvdh/laravel-ide-helper": "v2.2.1", "laravelcollective/html": "v5.3.0", "infyomlabs/laravel-generator": "5.3.x-dev", "infyomlabs/core-templates": "5.3.x-dev", "infyomlabs/swagger-generator": "dev-master", "jlapp/swaggervel": "2.0.x-dev", "doctrine/dbal": "2.3.5", "league/flysystem-aws-s3-v3": "1.0.13", "tymon/jwt-auth": "0.5.9", "barryvdh/laravel-cors": "v0.8.2", "fzaninotto/faker": "~1.4", "caouecs/laravel-lang": "3.0.19", "asvae/laravel-api-tester": "^2.0" }, "require-dev": { "fzaninotto/faker": "~1.4", "mockery/mockery": "0.9.*", "phpunit/phpunit": "~5.7", "symfony/css-selector": "3.1.*", "symfony/dom-crawler": "3.1.*" }, "autoload": { "classmap": [ "database" ], "psr-4": { "App\": "app/" } }, "autoload-dev": { "psr-4": { "Tests\": "tests/" } }, "scripts": { "post-root-package-install": [ "php -r "file_exists('.env') || copy('.env.example', '.env');"" ], "post-create-project-cmd": [ "php artisan key:generate" ], "post-install-cmd": [ "Illuminate\Foundation\ComposerScripts::postInstall", "php artisan optimize" ], "post-update-cmd": [ "Illuminate\Foundation\ComposerScripts::postUpdate", "php artisan optimize" ] }, "config": { "preferred-install": "dist" } }
UPDATE
Thanks to the answer given by “Basheer Ahmed” who pointed me in the right direction I ended up doing a Trait for parsing the body
attributes that I want to get depending on the request:
<?php namespace KeepClearTraitsControllers; trait ApiRequest { /** * Parse all attributes and return an array with the present values only. * * @param array $attributes * @param Request $request * * @return Array */ public function parseBody($attributes, $request) { $params = []; foreach ($attributes as $attribute) { $value = $request->request->get($attribute); if ($value) { $params[$attribute] = $value; } } return $params; } }
This method it will be used mostly on create
and update
actions like follows, on AddressController
:
<?php namespace KeepClearHttpControllersAPIV1; ... use KeepClearTraitsControllersApiRequest; ... class AddressController extends Controller { use ApiRequest; /** * Instantiate a new controller instance. * * @return void */ public function __construct() { $this->middleware('jwt.auth'); } ... /** * Create address for the specified user. * * @param Request $request * @param String $user_id * * @return Response */ public function createUserAddress(Request $request, $user_id) { try { $attributes = ['city', 'county_province', 'zip_code']; $params = $this->parseBody($attributes, $request); User::find($user_id)->addresses()->create($params); return Response::json( array( 'message' => 'The address was successfully created.', 'success' => true ) ); } catch (Exception $exception) { return Response::json( array( 'success' => false, 'exception' => $exception ) ); } } ... /** * Update address for the specified user. * * @param Request $request * @param String $user_id * @param String $address_id * * @return Response */ public function updateUserAddress(Request $request, $user_id, $address_id) { try { $attributes = ['city', 'county_province', 'zip_code']; $params = $this->parseBody($attributes, $request); Address::where(["user_id" => $user_id, "id" => $address_id]) ->update($params); return Response::json( array( 'message' => 'The address was successfully updated.', 'success' => true ) ); } catch (Exception $exception) { return Response::json( array( 'success' => false, 'exception' => $exception ) ); } } ... }
In this way and by using $request->request->get('my_param');
I can be sure after testing how that method works, that I’m only getting the attributes of the body.
This is the test for AddressController
on those methods:
<?php namespace TestsApi; use TestsTestCase; ... use IlluminateFoundationTestingWithoutMiddleware; use IlluminateFoundationTestingDatabaseMigrations; use IlluminateFoundationTestingDatabaseTransactions; use FakerFactory; ... class AddressControllerTest extends TestCase { use ApiTestTrait; use DatabaseMigrations; use WithoutMiddleware; ... public function testMethodCreateUserAddress() { $admin = factory(Role::class, 'admin')->create(); $user = $admin->users()->save(factory(User::class)->make()); $uri = 'api/v1/users/' . $user->id . '/addresses'; $faker = Factory::create(); $attributes = array( 'city' => $faker->city, 'county_province' => $faker->state, 'zip_code' => $faker->postcode ); $this->json('POST', $uri, $attributes) ->seeStatusCode(200) ->seeJsonEquals( [ 'message' => 'The address was successfully created.', 'success' => true ] ); } ... public function testMethodUpdateUserAddress() { $admin = factory(Role::class, 'admin')->create(); $user = $admin->users()->save(factory(User::class)->make()); $address = $user->addresses()->save(factory(Address::class)->make()); $uri = 'api/v1/users/' . $user->id . '/addresses/' . $address->id; $attributes = array( 'city' => 'newCity', 'county_province' => 'newCountyProvince', 'zip_code' => 'newZipCode' ); $this->json('PUT', $uri, $attributes) ->seeStatusCode(200) ->seeJsonEquals( [ 'message' => 'The address was successfully updated.', 'success' => true ] ); $updated_address = Address::find($address->id); $this->assertEquals($updated_address->city, 'newCity'); $this->assertEquals( $updated_address->county_province, 'newCountyProvince' ); $this->assertEquals($updated_address->zip_code, 'newZipCode'); } ... }
Advertisement
Answer
Anything that is appended to the url bar is considered a get request and will be available through $_GET
super global variable. I assume that laravel Request
request will merge both post and get request and then when you try to call any paramter that is sent through get or post, You can get it through
$request->myparam
But If you just try
$request->request->get('my_param');
You won’t get the similar result.
🙂