I am creating a controller that receive an AJAX request and from Laravel documentation, i can send header with X-Csrf token
https://laravel.com/docs/5.5/csrf#csrf-x-csrf-token
On my Controller i have something like this :
public function checkPromotion(Request $request)
{
try {
$this->middleware('VerifyCsrfToken');
}
catch (TokenMismatchException $e){
return response()->json(['error' => 'Error Token Provided']);
}
}
}
When i tried and sent a blank post request to this controller , the respond was blank .
There are three issues here:
If you are going to add middleware in a controller, you must do so in the constructor.
Out of the box, you cannot handle middleware exceptions in a controller action. You'll need to handle the exception in your \App\Exceptions\Handler class. The handle method of that class will receive the TokenMismatchException when token verification fails.
The string 'VerifyCsrfToken' is not a valid way to reference your middleware.
Regarding #3, the middleware method takes the name of a middleware group, or the FQCN of a particular middleware.
The following should work:
$this->middleware(\App\Http\Middleware\VerifyCsrfToken::class)
(I'm assuming that you are using the default App namespace)
If you get a "Session store not set on request" exception, it's because cannot use CSRF middleware without the StartSession middleware.
Most likely what you really want is the web middleware:
$this->middleware('web')
This will include the CSRF middleware, the session start middleware, and a few others (see your http kernel for details).
If needed you can exclude routes from CSRF verification by using the $except array in your VerifyCsrfToken class
The middleware method on Controller is registering a middleware in an array on the controller. That middleware is not ran at that point.
When this method is called in a constructor the router/route collection will have access to the getMiddleware method after the Controller has been resolved to be able to build the middleware stack needed for the current route/action.
You may want to be dealing with this exception in your exception handler, App\Exceptions\Handler.
Laravel 5.5 Docs - Errors - The Exception Handler - Render Method
public function render($request, Exception $exception)
{
if ($exception instanceof \Illuminate\Session\TokenMismatchException) {
return response()->json(['error' => 'Error Token Provided']);
}
return parent::render($request, $exception);
}
protected function tokensMatch($request)
{
// If request is an ajax request, then check to see if token matches token provider in
// the header. This way, we can use CSRF protection in ajax requests also.
$token = $request->ajax() ? $request->header('X-CSRF-Token') : $request->input('_token');
return $request->session()->token() == $token;
}
Add this function inside VerifyCsrfToken.php
Related
im working with laravel 5.5 and tried to protect an api route. I assigned the 'auth' middleware to this route, but when i tested it, i get an InvalidArgumentException aka 'Route [login] is not defined'. Im not reffering to this route in any way, laravel automaticaly tried to redirect to this route. I found the following code line in file 'laravel\framework\src\Illuminate\Foundation\Exceptions\Handler.php':
/*
* Convert an authentication exception into a response.
*
* #param \Illuminate\Http\Request $request
* #param \Illuminate\Auth\AuthenticationException $exception
* #return \Illuminate\Http\Response
*/
protected function unauthenticated($request, AuthenticationException $exception)
{
return $request->expectsJson()
? response()->json(['message' => $exception->getMessage()], 401)
: redirect()->guest(route('login'));
}
So im wondering, whats the best way to catch this exception globally on every route?
From my understanding, its because you added an auth middleware to the route. So whenever the route is accessed without an authentication, the route is redirected to the name login route which is the login page by default.
You can add an unauthenticated method to your app/Exceptions/Handler.php with the following code. So if the request is in API or expecting JSON response, it will give a 401 status code with JSON response. If the route is accessed using web routes, it will be redirected to the default page. In the below example, am redirecting it to a login page.
Note: This method existed in the app/Exceptions/Handler.php till laravel 5.4.
protected function unauthenticated($request, AuthenticationException $exception)
{
if ($request->expectsJson()) {
return response()->json(['error' => 'Unauthenticated.'], 401);
}
return redirect()->guest(('login'));
}
Don't forget to import use Illuminate\Auth\AuthenticationException;
Edit: Explanation for the error Route [login] is not defined. This is because laravel is trying to redirect to a named route. This named route is available if you are using the laravel auth scaffolding. If you are manually creating the functions, even if the route login exists, it should be named as login. For more information, see https://laravel.com/docs/5.5/routing#named-routes
I'm currently re-writing an API with multiple endpoints. However, for legacy purposes it's required that there is a single endpoint which can be used to access all other endpoints. Which endpoint we should redirect to is based upon a custom action header send along with the request.
Example:
Input: Header -> Action A
Output: Redirect to route '/some/url' 'ControllerA#someAction'
Input: Header -> Action B
Output: Redirect to route '/some/other/url' 'ControllerB#someOtherAction'
Normally, I could use the redirect() method but then I lose the body of the POST method. All the endpoints I declared are POST methods.
Basically, the question is how can I properly redirect a POST to another route?
Also note I can't use:
App::call('App\Http\Controllers\PlanningController#addOrUpdate', ['request' => $request]);
Since my method uses a custom Request class to handle the validation. I get an exception telling the argument should be the type of my custom class and Illuminate\Http\Request is given.
I've actually found the answer to my problem. I've created a middleware which will re-create the request based upon the value found in the header.
Here's the handle function of the middleware (only tested on Laravel 5.2):
use Request;
use Route;
use Illuminate\Http\Response;
...
public function handle($request, Closure $next, $guard = null)
{
// Get the header value
$action = $request->header('action');
// Find the route by the action name
$route = Actions::getRouteByName(action); // This returns some route, i.e.: 'api/v1/some/url'
// Perform the action
$request = Request::create(route, 'POST', ['body' => $request->getContent()]);
$response = Route::dispatch($request);
return new Response($response->getContent(), $response->status(), ['Content-Type' => 'text/xml']); // the last param can be any headers you like
}
Please note that this might conflict on your project with other middleware. I've disabled other middleware and created a special routegroup for this. Since we're redirecting the call manually to another route the middleware on that route is called anyway. However, you can also implement this code inside a controller function then there are no conflicting middleware problems!
In Laravel 5, if basic auth fails for a user then the default message that is returned is an "Invalid Credentials" error string. I am trying to return a custom JSON error when this situation occurs.
I can edit the returned response in vendor/laravel/framework/src/Illuminate/Auth/SessionGuard.php
however I have not seen where you can change the behavior of this message outside of the vendor directory. Is there a way?
Looks like there were some ways to do this through Laravel 4: Laravel 4 Basic Auth custom error
Figured it out, looks like I had to create custom middleware to handle this.
Note that this solution didn't work when calling my API from my browser, only when calling it from a tool like Postman. For some reason when calling it from my browser I always got the error before seeing the basic auth prompt.
In my controller I changed the middleware to my newly created one:
$this->middleware('custom');
In Kernel I added the location for it:
protected $routeMiddleware = [
'auth.basic.once' => \App\Http\Middleware\Custom::class,
]
Then I created the middleware. I used Stateless Basic Auth since I'm creating an API:
<?php
namespace App\Http\Middleware;
use Auth;
use Closure;
use Illuminate\Http\Request as HttpRequest;
use App\Entities\CustomErrorResponse
class Custom
{
public function __construct(CustomErrorResponse $customErrorResponse) {
$this->customErrorResponse = $customErrorResponse
}
public function handle($request, Closure $next)
{
$response = Auth::onceBasic();
if (!$response) {
return $next($request);
}
return $this->customErrorResponse->send();
}
}
I am today in bit confusion about my website security and some extra code that is written to make the website secure. Below are 2 locations where security is applied.
Inside Route Config, To secure the route, I have used Middleware to check the user role.
Route::group(['middleware' => ['web', 'SuperAdmin', 'auth']], function () {
Route::get('/Create-Department', 'DepartmentController#CreateDepartment');
});
I mentioned 2 Middlewares.
Auth Middleware : This is for authentication.
SuperAdmin Middleware: This is for Authorization.
Second location is Request class. Here is the code. In authorize method, again same thing is being checked as already done in route
class DepartmentRequest extends Request
{
public function authorize()
{
if(\Auth::user() == null) {
return false;
}
if(\Auth::user()->isSuperAdmin()) {
return true;
}
return false;
}
public function rules()
{
return [
'Department' => 'required',
];
}
}
Question: Should I remove check in Request class? Is that an unwanted validation to secure the request ? As route.config is already doing the job.
What's the use of authorize method? I meant, I am using Request class to validate form inputs. Should I always return true from authorize method?
yes, you should remove that checks in the Request class: if you're already doing that checks in your middleware you should not repeat them
When you specify this:
Route::group(['middleware' => ['web', 'SuperAdmin']], function () {
Route::get('/Create-Department', 'DepartmentController#CreateDepartment');
});
You're telling laravel that, when it finds a /Create-Department route, it should trigger the handle method of these middleware: ['web', 'SuperAdmin'], before the request is sent to the DepartmentController
So, if you check for authentication and authorization in the middlewares, when the request will get to your controller you're sure that it has satisfied all the middleware it went through
Regarding the purpose of the authorize method: the authorize method is usually used to authorize the actual request basing on some policy you'd like to respect. For example, if you have a request to edit a Post model, in the authorize method you'd check that the specific user trying to edit the post has the permissions to do it (for example being the author of the post )
EDIT
Even if you want to use a middleware for your authorization, it's fine. Anyhow, usually the authorize method within form requests is used to do authorization checks on the specific request.
For instance check this example from the docs :
public function authorize()
{
$postId = $this->route('post');
//here the authorization to edit the post is checked through the Gate facade
return Gate::allows('update', Post::findOrFail($postId));
}
In conclusion: if you're doing your authentication and authorization tasks in middlewares, you don't need to repeat them in the authorize method, but keep in mind that the native purpose of the method is to authorize the specific request
I have this in Laravel 4.2 Route::when('*', 'csrf', ['post']); that insert csrf verification to all post, how can I port to Larevel 5.2 ?
This is my own csrf, without using default provide by Laravel:
<?php
namespace App\Http\Middleware;
use Closure;
use Input;
class VerifyCsrfToken1
{
public function handle($request, Closure $next)
{
$token = $request->ajax() ? $request->header('X-CSRF-Token') : $request->input('_token');
if ($request->session()->token() === $token) {
return $next($request);
}
throw new TokenMismatchException;
}
}
I created my personal csrf middleware, but I don't know how to attach them on ALL post request
I want to attach it to all post via Route's facade. (file routes.php)
Thanks :)
Laravel 5 wires up middleware a bit differently, you won't be doing this through the Request facade.
You want to first register your Middleware as global. Open up app/Http/Kernel.php and add it to the global $middleware array.
protected $middleware = [
VerifyCsrfToken1::class
...
Then in your middleware class, check to see if it is handling a POST request. If not, have it just pass the request along without doing anything.
if($request->method() != "POST") {
// Move right along
return $next($request);
}
Side note: As you noted Laravel has a VerifyCsrfToken middleware baked in already. I'd advise trying to adapt this if possible.