Skip to content
Advertisement

How to prevent Laravel API from processing parameters on query-string?

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:

enter image description here

Here is the POST by using parameters in the body:

enter image description here

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:

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.

🙂

User contributions licensed under: CC BY-SA
1 People found this is helpful
Advertisement