Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Prevent Laravel API from processing the token if it comes in the URL as querystring

I'm able to process the token either if it comes in the URL as a querystring or if it comes in the header as Authentication with the token being prefixed with Bearer, and I only want to be able to receive it in the header.

This is my app/Http/Controllers/API/V1/AuthenticationController.php file:

<?php

namespace app\Http\Controllers\API\V1;

use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use Tymon\JWTAuth\Exceptions\JWTException;
use App\Models\Role;
use App\Models\User;
use App\Traits\Controllers\ApiParseBody;
use App\Traits\Controllers\ApiException;
use App\Traits\Controllers\ApiEvaluateCredentials;
use Tymon\JWTAuth\JWTAuth;
use App\Exceptions\Unauthorized\InvalidCredentials;
use App\Exceptions\InternalServerError\CouldNotCreateToken;
use Illuminate\Contracts\Hashing\Hasher;

class AuthenticationController extends Controller
{
    use ApiParseBody;
    use ApiEvaluateCredentials;
    use ApiException;

    /**
     * The user implementation.
     *
     * @var User
     */
    protected $user;


    /**
     * The role implementation.
     *
     * @var Role
     */
    protected $role;

    /**
     * The hash implementation.
     *
     * @var Hash
     */
    protected $hash;

    /**
     * The jwtauth implementation.
     *
     * @var JWTAuth
     */
    protected $jwtauth;

    /**
     * Instantiate a new controller instance.
     *
     * @return void
     */
    public function __construct(
        User $user,
        Role $role,
        Hasher $hash,
        JWTAuth $jwtauth
    ) {
        $this->middleware('jwt.auth', ['except' => ['signin', 'signup']]);
        $this->user = $user;
        $this->role = $role;
        $this->hash = $hash;
        $this->jwtauth = $jwtauth;
    }

    /**
     * Signin user.
     *
     * @param Request $request
     *
     * @return Response
     */
    public function signin(Request $request)
    {
        $attributes = array('email', 'password');
        $credentials = $this->parseBody($attributes, $request);
        $this->validateCredentialsArePresent($credentials);
        try {
            if (! $token = $this->jwtauth->attempt($credentials)) {
                throw new InvalidCredentials('invalid_credentials');
            }
        } catch (JWTException $e) {
                throw new CouldNotCreateToken('could_not_create_token');
        }
        return response()->json(compact('token'));
    }

    /**
     * Signup user. Default role is 'common'.
     *
     * @param Request $request
     *
     * @return Response
     */
    public function signup(Request $request)
    {
        $attributes = array('email', 'password');
        $params = $this->parseBody($attributes, $request);
        $this->validateCredentialsArePresent($params);
        $this->evaluateCredentials($params);
        $credentials = array(
            'email' => $params['email'],
            'password' => $this->hash->make($params['password'])
        );
        $this->validateUserAlreadyExists($credentials);
        $commonRole = $this->role->where('name', 'common')->firstOrFail();
        $user = new User($credentials);
        $commonRole->users()->save($user);
        return response()->json(array( 'message' => 'User signed up.'));
    }
}

This is my config/cors.php file:

<?php

return [
   'defaults' => [
       'supportsCredentials' => false,
       'allowedOrigins' => [],
       'allowedHeaders' => [],
       'allowedMethods' => [],
       'exposedHeaders' => [],
       'maxAge' => 0,
       'hosts' => [],
   ],

   'paths' => [
       'v1/*' => [
           'allowedOrigins' => ['*'],
           'allowedHeaders' => [
               'Origin',
               'Content-Type',
               'Accept',
               'Authorization',
               'X-Request-With'
           ],
           'allowedMethods' => ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'],
           'exposedHeaders' => ['Authorization'],
           'maxAge' => 3600,
       ],
   ],
];

The following images will show what I mean, just in case I wasn't clear with what I'm trying to transmit.

This one shows how I use Postman for doing a GET to the app on Heroku. You will see that I'm using the header Authorization:

enter image description here

And what I want to prevent is to get the same result by sending the token in the URL as follows:

enter image description here

I don't even know if this is possible, so I would really appreciate any kind of guidance in this matter.

like image 671
nisevi Avatar asked May 03 '17 15:05

nisevi


People also ask

How can check access token is valid or not in Laravel?

If you don't want to use the Passport middleware in the project where you want to validate the tokens, you would have to create an endpoint in the Laravel Passport server that can accept the token, perform the usual Passport validation and return a response to your service.

How API token works in Laravel?

By default, Laravel ships with a simple solution to API authentication via a random token assigned to each user of your application. In your config/auth. php configuration file, an api guard is already defined and utilizes a token driver.

What is $request in Laravel?

Laravel's Illuminate\Http\Request class provides an object-oriented way to interact with the current HTTP request being handled by your application as well as retrieve the input, cookies, and files that were submitted with the request.


1 Answers

What I did was to create a middleware in order to reject all requests with "token" as a key param in the querystring.

First we have to create the middleware:

php artisan make:middleware BeforeMiddleware and as you may notice is a before middleware, that means that is going to run before the request hits the application:

<?php

namespace App\Http\Middleware;

use Closure;
use App\Exceptions\BadRequest\RejectTokenAsQuerystring;

class BeforeMiddleware
{
    /**
     * Handle an incoming request.
     *
     * @param \Illuminate\Http\Request  $request
     * @param \Closure  $next
     *
     * @return mixed
     */
    public function handle($request, Closure $next)
    {
        if ($request->token) {
            throw new RejectTokenAsQuerystring('reject_token_as_querystring');
        }
        return $next($request);
    }
}

I also had to add the middleware that I created to my kernel:

<?php

namespace App\Http;

use Illuminate\Foundation\Http\Kernel 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 = [
        \Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::class,
        \Barryvdh\Cors\HandleCors::class,
    ];

    /**
     * The application's route middleware groups.
     *
     * @var array
     */
    protected $middlewareGroups = [
        'api' => [
            'throttle:60,1',
            'bindings',
        ],
    ];

    /**
     * The application's route middleware.
     *
     * These middleware may be assigned to groups or used individually.
     *
     * @var array
     */
    protected $routeMiddleware = [
        'reject-token-in-url' => \App\Http\Middleware\BeforeMiddleware::class,
        'auth' => \Illuminate\Auth\Middleware\Authenticate::class,
        'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
        'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,
        'can' => \Illuminate\Auth\Middleware\Authorize::class,
        'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
        'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
        'jwt.auth' => \Tymon\JWTAuth\Middleware\GetUserFromToken::class,
        'jwt.refresh' => \Tymon\JWTAuth\Middleware\RefreshToken::class,
    ];
}

And finally the middleware as is being defined globally can be used in the definition of my routes as:

<?php

/*
|--------------------------------------------------------------------------
| 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(
    [
        'domain' => getenv('API_DOMAIN'),
        'middleware' => ['cors', 'reject-token-in-url'],
        'prefix' => '/v1',
        'namespace' => 'V1'
    ],
    function () {
    }
);

I also have implemented my own definition of error, so I have a list of all possible errors that I want to trigger in my application, and they are defined as follows in my config/errors.php file:

<?php

return [
    "reject_token_as_querystring" => [
            "title"  => "Reject token as querystring.",
            "detail"  => "Token MUST be passed in the Header of the request."
    ]
];

Then you need to define your custom Exception class:

<?php

namespace App\Exceptions;

use Exception;

abstract class CustomException extends Exception
{
    /**
     * The id of the error that is being triggered.
     *
     * @var string
     */
    protected $errorId;

    /**
     * Status code for the triggered error.
     *
     * @var string
     */
    protected $status;

    /**
     * Title of the error.
     *
     * @var string
     */
    protected $title;

    /**
     * Detailed description about the error.
     *
     * @var string
     */
    protected $detail;

    /**
     * Instantiate a new Exception with the provided message.
     *
     * @param @string $message
     *
     * @return void
     */
    public function __construct($message)
    {
        parent::__construct($message);
    }

    /**
     * Get the status
     *
     * @return Int
     */
    public function getStatus()
    {
        return (int) $this->status;
    }

    /**
     * Return the Exception as an array
     *
     * @return Array
     */
    public function toArray()
    {
        return [
            'id'     => $this->id,
            'status' => $this->status,
            'title'  => $this->title,
            'detail' => $this->detail
        ];
    }

    /**
     * Build the Exception.
     *
     * @param array $args
     *
     * @return string
     */
    protected function build(array $args)
    {
        $this->id = array_shift($args);
        $error = config(sprintf('errors.%s', $this->id));
        $this->title  = $error['title'];
        $this->detail = vsprintf($error['detail'], $args);
        return $this->detail;
    }
}

And you will use that class to extend your custom errors:

<?php

namespace App\Exceptions\BadRequest;

use App\Exceptions\CustomException;

class BadRequestException extends CustomException
{
    /**
     * Status error number.
     *
     * @var string
     */
    protected $status = '400';

    /**
     * Instantiate a new 'bad request exception'.
     *
     * @return void
     */
    public function __construct()
    {
        $message = $this->build(func_get_args());

        parent::__construct($message);
    }
}

In order to create the class that holds the error itself:

<?php

namespace App\Exceptions\BadRequest;

use App\Exceptions\BadRequest\BadRequestException;

class RejectTokenAsQuerystring extends BadRequestException
{

}

Finally if you try to request info with the token key in the url you will get:

{
  "id": "reject_token_as_querystring",
  "status": "400",
  "title": "Reject token as querystring.",
  "detail": "Token MUST be passed in the Header of the request."
}

enter image description here

like image 164
nisevi Avatar answered Oct 17 '22 17:10

nisevi