After upgrading to CakepPHP 4.0 my POST request trough XMLHttpRequest stopped passing data to $this->request->getData()
The data is accesible by $this->request->input('json_decode'); though and GET requests work as well.
But I wonder what has changed in comparision to 3.* and why it's not working like it was before.
This is my xhr:
this.$http.post(
url,
data,
{headers: {'X-CSRF-TOKEN': '<?= $this->request->getAttribute('csrfToken') ?>'}},
})
.then(response => {
//
}
);
It gives me an empty array when I call $this->request->getData()
I tried to turn off FormProtection component for that specific action but nothing changed.
If want to know what changed, check the out the migration guide first, in this case specifically the breaking changes section for components.
The request body parsing features have been removed from the request handler component (the deprecation warning that was in place previously has been removed too, as it was causing too many false positives). This should now be handled by the body parser middleware, which you need to add your application accordingly, either globally in your Application class':
public function middleware(MiddlewareQueue $middlwareQueue): MiddlewareQueue
{
// ...
$middlwareQueue->add(new \Cake\Http\Middleware\BodyParserMiddleware());
return $middlwareQueue;
}
or in a routing scope:
\Cake\Routing\Router::scope('/', function (\Cake\Routing\RouteBuilder $routes) {
$routes->registerMiddleware('bodyParser', new \Cake\Http\Middleware\BodyParserMiddleware());
$routes->applyMiddleware('bodyParser');
// ...
});
See also
Cookbook > Middleware > Using Middleware
Cookbook > Routing > Connecting Scoped Middleware
Related
I have been working on a simple Laravel Inertia Vue3 application. It has one resource route.
Route::resource('contact', \App\Http\Controllers\ContactController::class);
This provides the named routes contact.index .store .create .show .update .destroy and .edit
Nice and simple so far.
I have a useForm form variable in my Vue component with some assigned variables
let form = useForm({ ... });
I have a submit method in the same component
let submit = () => {
if(props.edit) {
form.patch(`/contact/${props.contact.id}`);
} else {
form.post(`/contact`);
}
}
Again nothing complex. The post method fires off correctly and redirects
Contact::query()->create($request->all());
return redirect()->to(route('contact.index'));
For full disclosure of the update method, please see below:
public function update(Request $request, Contact $contact): \Illuminate\Http\RedirectResponse
{
$contact->fill($request->all())->save();
return redirect()->to(route('contact.show', ['contact' => $contact]));
}
This works in the same way as store. Simple and then redirects... but it doesn't.
What happens is that it runs the patch and then calls redirect
The redirect carries the patch method through ending up with a 405 if I use the index route (declared as get). If I use back() I get the same thing. If I use the show route, it redirects in a loop because the patch route uses the same URL /contact/{contact}
I have built Laravel applications for the last 5 years and have not had an issue like this before when using a named route. Have I missed something basic? If not its possibly a configuration issue although I am not sure what.
I am running Laravel 9.19 with webpack manually installed as its been changed to Vite on the current release. I have no Vue errors or warnings and no Laravel logs.
there is a difference between PUT and PATCH requests on laravel-level.
Please run php artisan route:list - and see which one is used in your case, There is a big chance that you using PUT, not patch :)
So good ol' Laravel got me again.
Alexander Dyriavin got me on the right course with his answer about put and patch, however, it wasn't really the solution.
The solution:
form.transform((data) => ({
...data,
_method: 'PUT' //spoof added to request
})).post(`/contact/${props.contact.id}`); //sent as a post instead
The Laravel docs allow you to spoof methods https://laravel.com/docs/5.0/routing#method-spoofing by posting them with a _method field.
Simply put, a patch or put request would have always failed with a redirect from Laravel. In the past I would have used them with Axios and handled a JSON response directly.
This time I really am answering my own question.
I was a total idiot and missed a step when setting up inertia js. I was attempting to retrieve errors with the useform method and what happened was I received nothing.
So I though I would double check the docs.
Turns out I missed adding this middleware to the web middleware group in the kernel!
\App\Http\Middleware\HandleInertiaRequests::class,
I can now use the .patch method I had before and no need for any additional code
I am trying to get a laravel-nuxt project running. I am stuck with creating route calls to my laravel backend using axios async call to serve up data to my nuxt frontend before loading the page.
I am constantly getting getting a 404 with my current laravel-nuxt setup even though I have the route defined in api.php.
I am using this as a template for the project and I have not changed anything in that template yet:
https://github.com/cretueusebiu/laravel-nuxt
So my frontend call is this here:
async asyncData ({ $axios }) {
if (process.server) {
return $axios.$get('/api/data')
.then((res) => {
this.data = res.data;
})
}
}
And my backend route is defined as follows in api.php:
Route::get('/data', 'HomeController#index');
It always gives me a 404, is there something missing that I should be aware of?
According to the Readme in the Github project you have mentioned, you have to add your routes manually to
client/router.js
Read this line under Notes and follow the structure well you'll be able to avoid this.
This project uses router-module, so you have to add the routes
manually in client/router.js.
hope this helps.
I'm using Laravel 5.5
I am trying to set session variable on GET fetch('/ajax-get-save-session') request.
Route::get('/ajax-get-save-session', function() { session(['my_key', 'Saved Session']) });
And when I try to access the session variable my_key in different route.
Route::get('/access-session', function() { return session('my_key'); });
NULL value is returned.
This routes are registered on Laravel web.php and I'm using MDN fetch API
Anyone?
If i understand your problem. You are looking for this. Try this:-
Use Session; //if your are using laravel 5.2 then add in top in routes. php or laravel 5.2 > greater versions in web.php
// now put data in session
Route::get('/ajax-get-save-session', function() {
Session::put('my_key','Saved Session');
});
//Access the session
Route::get('/access-session', function() { echo Session::get('my_key') });
Hope it helps!
It's totally my mistake. I never thought fetch() API is the one causing my problem. It says the description
By default, fetch won't send or receive any cookies from the server, resulting in unauthenticated requests if the site relies on maintaining a user session (to send cookies, the credentials init option must be set).
By doing :
fetch(url, { credentials : 'include' }).then(response => response.json())
SOLVED MY PROBLEM!
I'm sending an ajax post request, and with Laravel it seems that is done by creating a post route for it. I've set it up so a csrf token is put in the header automaticaly for every ajax request using ajaxSetup. I'm attempting to then catch that header on the backend and verify the tokens match.
In my web routes (which automatically use the web middleware), this returns as expected:
Route::get('/test', function() {
return csrf_token();
});
However, when I post to a route via AJAX, like either of the below ways:
Attempt 1:
Route::post('/test', 'AjaxController#test');
In the AjaxController construct, followed by an alert in the view:
var_dump(csrf_token().',hi'); die;
Response: ',hi' (csrf_token was null).
Attempt 2:
Route::post('/test', ['test' => csrf_token().',hi', 'uses' => 'AjaxController#test']);
$test = $request->route()->getAction()['test'];
var_dump($test); die;
Response: ',hi' (csrf_token was null).
What I seem to be running into is, with get requests csrf_token() is populated, on my post request, it is not.
Any ideas?
check your route group it must apply the web middleware as
Route::group(['middleware' => 'web'], function () {
Route::get('/test', function() {
return csrf_token();
//or return $request->session()->token();
});
});
Finally figured this out.
CSRF can indeed be checked on an ajax post request. I wanted to make sure someone on their own site isn't hitting my ajax endpoint with any success of doing anything, especially for another user.
However, I ran into a Laravel order of operations issue, with the way Laravel sets up the session. I was trying to call a validation method (within in the same class) in the constructor, where I validated for CSRF and verified the requesting user all in one place. I wanted to do this so that any time someone hits this class, I didn't have to call the verification in each public method in the class, I'd only have to call it once.
However, csrf_token(), and the request session in general, is not available to me yet in my construct. It is, however, available to me in the method within the controller class that is called in the route.
For example, given the following route:
Route::post('/test', 'AjaxController#test');
If I injected Request into the construct and then tried to reference anything in the session (in the construct), or get the value of csrf_token(), it will throw an error, because Laravel hasn't set that stuff up yet. But if I reference either of those things in the test method, it'll be there and available just fine.
A bit of a weird Laravel order of operations issue.
csrf protections are managed by Laravel Forms. It won't be available when dealing with APIs.
You should have a look at how middlewares are used in Laravel
https://laravel.com/docs/5.4/middleware
Think using API middleware for your APIs ;)
If you run this command php artisan make:auth documented here https://laravel.com/docs/5.4/authentication#authentication-quickstart when going to resources/views/layouts/app.blade.php you'll see this:
<meta name="csrf-token" content="{{ csrf_token() }}">
And in app.js
$.ajaxSetup({
headers: {
'X-CSRF-TOKEN':$('meta[name="csrf-token"]').attr('content')
}
});
In 5.3 there was this cool feature which looks as though it has since been removed in 5.4.
<script>
window.Laravel = <?php echo json_encode([
'csrfToken' => csrf_token(),
]); ?>
</script>
So what you need to do is add the csrf field to every request. Do the first 2 code snippets and you'll be fine. The 3rd I believe is probably for Vue.
Answer to your question: no, no, no and no. CSRF tokens I wouldn't believe are generated in POST requests, it's a Cross site Reference token, not an authentication token. If you're looking for something like authentication token refreshing then checkout JWT although the packages for JWT for laravel are a bit unfinished at the moment; with a little work you can get them working.
https://github.com/tymondesigns/jwt-auth 1.0.*#dev is pretty good. You can then use their refresh middleware to generate new tokens on request but this is quite advanced and unless it's for authentication then I wouldn't bother really.
I believe Dingo (another work in progress I believe) https://github.com/dingo/api uses the above package
Anything else let me know!
I'm working in a RESTFul API with laravel, and I want to use content negotiation in my project, but I don't know how to accomplish that.
I have my controllers separted by api version, and I want to distinguish between api versions and use the correct controllerdepending on the version.
My API router is:
Route::group(array('prefix' => 'api'), function() {
Route::resource('users', 'API\V1\UsersController');
});
Should I create a api.type filter to use in my route group or Should I do that in the route group clousure or maybe, in each controller?
Don't be afraid to branch out your application logic into library classes. You don't have to fit everything inside of Laravel's given folder structure.
In fact, adding in your own namespaced group of classes can have a big benefit. You can see some setup on creating your own application library here.
Once you have that set up, you can create a class whose responsibility is to decide what content type to return. This might be based on an Accept header, URL parameter or whatever you define (That's up to you, the API creator).
Perhaps this class will take the Accept header and normalize it to something like "json", "xml" and "html".
The Request class has some methods to help you if you do it via the Accept header.
So, in pseudo code (syntax errors to follow!), your controller might do something like this:
/* GET /api/users */
public function index()
{
// Might return "json" or "xml" or "html"
$contentType = \My\ContentType\Class->getContentType();
$users = User::all();
// Not shown here is logic to set `content-type` header in the returned response (json vs xml vs html)
// Perhaps a method to go into your implementation of \My\ContentType\Class
return View::make('users.'.$contentType)->with(array( 'users' => $users ));
}
That's just an idea of what you might do. The key point is to get yourself working in a library that you can put business logic into to get you started with an idea of how to accomplish adding in business logic for your application.
Hope that helps.