Yii2 CORS inserts two records - ajax

I am building an API with Yii2 and have enabled the CORS filter to handle requests from a web frontend which is working.
However because of the pre-flight OPTIONS request and then the real POST request I am getting two records added to the database, one for each request. I would have thought that Yii should accept the OPTIONS request, return the correct headers and then exit. Why does it actually process the full request?
I am working around this for now by adding this to the top of the controller action:
if(Yii::$app->request->getMethod() == 'OPTIONS') {
return;
}
Is that the best approach or am I missing something?

That should be wrong because a browser need the options response to know the allowed list of verbs he can send. Otherwise a 401 error may be raised. Its source code can be seen here:
class OptionsAction extends \yii\base\Action
{
public $collectionOptions = ['GET', 'POST', 'HEAD', 'OPTIONS'];
public $resourceOptions = ['GET', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS'];
public function run($id = null)
{
if (Yii::$app->getRequest()->getMethod() !== 'OPTIONS') {
Yii::$app->getResponse()->setStatusCode(405);
}
$options = $id === null ? $this->collectionOptions : $this->resourceOptions;
Yii::$app->getResponse()->getHeaders()->set('Allow', implode(', ', $options));
}
}
And that is all what it does: sending a list of allowed verbs within a response headers.
Maybe the POST request has been sent twice from client script due to unexpected responses. Try to apply the answer I posted in your other question instead. I think it will also solve this:
Yii2 CORS with Auth not working for non CRUD actions.

Related

Axios AJAX call nulls parameter

I use Vuejs to create my frontend for my project.
At the creation of one component ('TimeCapsy.vue'), I make an AJAX call to my backend like this:
created: function () {
if (verify.verify_login()) {
let token = this.$cookies.get('jwt_us_cas');
let params = {'jwt': token};
console.log(params);
axios({
method: 'post',
url: dev.HOST+'getuserinfoobject',
params: queryString.stringify(params)
})
.then(response => {
console.log(response.data)
})
}
}
As you can see I use the
this.$cookies.get('jwt_us_cas');
to get the a json web token, that I set on the client at the login.
I use the queryString Library to stringify my parameters for my request.
I also tried it without the queryString.stringify(params) call, but I get the same error, e.g. the parameter still turns into null.
When I look at the console log, where I check the params variable, I get this output:
{jwt: "my token comes here"}
So I can see, that it gets the correct value from the cookie.
But when I check the answer from my backend (PHP), I get this error:
Undefined index: jwt in <b>D:\casb\public\index.php</b> on line <b>52</b>
Of course I know that it means, that jwt is null, but I can't understand why.
As I said, right before I make the call I check the params and it shows the token.
I checked the endpoint with Postman and the token as the jwt parameter and it returned a successfull call with the correct answer.
A correct answer is basically just a nested object with some information in it.
My PHP endpoint is pretty basic too:
Router::add('/getuserinfoobject', function () {
$response['response'] = User::getUserInfoObject($_POST['jwt']);
echo json_encode($response);
}, 'post');
So I guess that right before or in my call it nulls my parameter. But I can't understand how, since I make a lot of requests and never had this problem.
From axios docs
params are the URL parameters to be sent with the request
Which means, you should get the value with PHP $_GET.
Or $_REQUEST (which stores both $_GET, $_POST. Also $_COOKIE).
The other hand, you can use data key as docs says
data is the data to be sent as the request body
Only applicable for request methods PUT, POST, and PATCH
So the value would be available in $_POST
axios({
method: 'post',
url: dev.HOST+'getuserinfoobject',
data: {
jwt: token
}
})

POST Request to other server

I want to send a POST request to an other server with Ajax when a button is pressed.
But I'm getting the error message:
XMLHttpRequest cannot load https://www.example.com/hello. Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'https://my.site.com' is therefore not allowed access.
This is my button:
<button id="my-button">Click me pls</button>
And this is the JS code:
document.getElementById("my-button").addEventListener("click", function (evt) {
var request = new XMLHttpRequest();
request.open('POST', 'https://www.example.com/hello', true);
request.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8');
request.setRequestHeader('X-CSRF-TOKEN', "<...>");
request.onreadystatechange = function () {
if (request.readyState == 4 && request.status == 200) {
console.log(request.responseText);
}
};
request.send("message=Thisismymessage&" +
"_token=<...>");
evt.preventDefault();
return false;
});
/hello should process and store the message in the database.
On the server side I'm using Laravel 5.4.
This is my route:
Route::post('/hello', 'Auth\RegisterController#hello')
->middleware('cors');
The cors Middleware looks like this:
namespace App\Http\Middleware;
use Closure;
class Cors
{
public function handle($request, Closure $next)
{
return $next($request)
->header('Access-Control-Allow-Origin', 'https//my.site.com')
->header('Access-Control-Allow-Methods', 'GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS')
->header('Access-Control-Allow-Headers', 'origin, content-type, accept, authorization');
}
}
The hello method in the RegisterController just takes the data (in this case the message "Thisismymessage") and puts it in the database.
protected function hello(Request $request)
{
// Working with the data...
}
Do any of you have an idea how to fix it?
And my additional question ist: is there a way to "generate" the CSRF token from Laravel from an other application which doesn't use Laravel as framework or do I have to copy & paste it manually?
Thank you in advance.
I could recommend a little hack to solve the second question, i.e:
is there a way to "generate" the CSRF token from Laravel from an other application which doesn't use Laravel as framework or do I have to copy & paste it manually?
Create an end point in the backend of your app to generate a view of the form. You should already include the csrf_token field when making this form, so that at your front end you have a complete form which you will submit again.
This look like a snake long way.
The more recommended way is that: it appears you are building an api, this makes it easy because its stateless. Use an api key example with JWT-AUTH, so that you don't have to deal any thing with csrf token.
For the first question:
You should just sort out the cors issue from your backend a good one is Cors middleware for Laravel 5
Others: Not recommended but for test purpose you can add these in the constructor of your controller to see how your app fair:
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: POST, GET, OPTIONS, PUT, DELETE');
header('Access-Control-Allow-Headers: Content-Type, Accept, Authorization, X-Requested-With, Application');
Remember: This is useful only if you are doing a test.

how to fix bug laravel validator with Post man send API. Validator return redirect uri root path. it not errors json status 422

how to fix bug laravel validator with Post man send API. Validator return redirect uri root path. it not errors json status 422.
Laravel redirect to root on request validation error .
I have a required field in a Laravel Request Class and when that field is not present in the request, the request is redirected to root '/'.
I am sending the request via Postman.
Please add Accept: application/json in you header.
Laravel redirect validator root url "/" by it check ajax == false. if it return true, it run
If ($ this-> expectsJson ()) {
             Return new JsonResponse ($ errors, 422);
         }
I solved the problem by adding setup POST MAN in Headers:
I hope it will be useful to the next person when the validator does not work as you expect, you need to set it up like sending ajax to the routes api:
"Key" => "value"
X-Requested-With => XMLHttpRequest
Post Man need config setting some ajax
Otherwise laravel will not be able to return error code 422.
If ($ this-> expectsJson ()) {
             Return new JsonResponse ($ errors, 422);
         }
==> return false if not have "X-Requested-With" : "XMLHttpRequest" in headers POST MAN.
ajax or not ajax active other #.
sorry i do not know english.

A 405 status code from web API after trying to send PUT data in body

ok.
I'm using Web API to make AJAX requests.
I'm trying to send a PUT request to an action on a controller.
I'm using route attributes.
When I'm sending the data as part of the route data, everything is fine and the action gets the right info.
However, when I'm trying to send the data in the body, I get a 405 status ((Method is not allowed).
I'm also adding the [FromBody] attribute to the parameter. Here's by jQuery call:
type: 'PUT',
url: 'api/ServerQueue/activity',
data: "=2",
success: function (xhr) {
$("#load").hide();
},
error: function () {
$("#load").hide();
}
};
Here's my action:
[Route("status/{status}")]
public string PutStatus([FromBody]int status)
{
}
I placed a "RoutePrefix" on the controller body.
BTW, I'm using VS 2012.
Any idea what could be the source of the problem?
Try changing the route configuration from
[Route("status/{status}")]
to
[Route("status")]

Django Angular Authentication CSRF cached template

I am getting status code 403 when I try to log in after successfully being logged in and logged out.
Client side is written in Angular and server side is in Django.
This goes as follows:
Client requests url '/' fetches main HTML template with all required static files ( angular, bootstrap, jQuery sources and angular sources defined by me) with
<div ng-view></div> tag into which further templates will be inserted.
Via $location service is redirected to url '/#/login'
This rule from $routeProvider is executed once '/#/login' is hit:
$routeProvider.when('/login', {
templateUrl: 'login.html'
});
'login.html' is served by django view and form for logging in is rendered to the user
User logs in successfully providing proper credentials
Then user logs out, by clicking on a button, which fires '$http.get(
'/logout/'
);' and then is redirected to url '/#/login'
Here is the problem. When user fills in credential form and sends 'POST' request, 403 is returned. I thought that it is, because this routing is done only by angular and since 'login.html' template has already been requested it has been catched and can be served without hitting backend, but after logging out currently possesed CSRF cookie is stale, so that's why I am getting 403. So I tried to remove that template:
logout: function(){
var forceLoginTemplateRequest = function(){
if( $templateCache.get('login.html') !== 'undefined'){
$templateCache.remove('login.html');
}
};
var responsePromise = $http.get(
urls.logout
);
responsePromise.success(forceLoginTemplateRequest);
return responsePromise;
}
After doing that I could see client side requesting 'login.html' template always after logging out, so I thought I could provide CSRF cookie when serving that template from backend:
#urls.py
urlpatterns = patterns(
'',
...
url(r'^$', serve_home_template),
url(r'^login.html$', serve_login_template),
url(r'^login/', login_view, name='login'),
url(r'^logout/', logout_view, name='logout'),
...
)
#views.py
#ensure_csrf_cookie
def serve_login_template(request):
return render(request, "login.html")
#ensure_csrf_cookie
def serve_home_template(request):
return render(request, 'home.html')
But still it doesn't work and I am getting 403 when trying to log in after logging out. The only way I managed it to work is to simply refresh the page so that every single file, whether template or source file is requested again from the backend and CSRF cookie is updated with them.
Here is my app's run section for making sure CSRF cookie is sent with every request:
mainModule.run(['$http','$cookies', '$location', '$rootScope', 'AuthService', '$templateCache',
function($http, $cookies, $location, $rootScope, AuthService, $templateCache) {
$http.defaults.headers.common['X-CSRFToken'] = $cookies.csrftoken;
$rootScope.$on( "$routeChangeStart", function(event, next, current) {
if ( !(AuthService.isLoggedIn() == "true")){
$location.path('/login');
}
});
}]);
This could be a cache problem. Try to add the never_cache decorator to all your views:
from django.views.decorators.cache import never_cache
...
#ensure_csrf_cookie
#never_cache
def serve_login_template(request):
return render(request, "login.html")
...
I solved this problem by setting X-CSRFTOKEN header in $routeChangeStart event.
I don't exactly know how module.run phase works, but it seems that when certain event defined within it occurs everything what is defined outside this event's handler body isn't executed.
mainModule.run(['$http','$cookies', '$location', '$rootScope', 'AuthService',
function($http, $cookies, $location, $rootScope, AuthService) {
$http.defaults.headers.common['X-CSRFToken'] = $cookies.csrftoken;
$rootScope.$on( "$routeChangeStart", function(event, next, current) {
// Added this line
$http.defaults.headers.common['X-CSRFToken'] = $cookies.csrftoken;
if ( !(AuthService.isLoggedIn() == "true")){
$location.path('/login');
}
});
}]);
This works together with removing 'login.html' template from $templateCache.
Instead of removing templates on client side with $templateCache service it is also possible to set your server to serve templates and set following headers:
Cache-Control: no-cache, no-store, must-revalidate
Pragma : no-cache
Expires : 0
Another way of dealing with this problem is to simply force page refresh, however I don't like this approach, since this is not pro-single-page-app approach. :)
One solution could be to read the current, fresh csrftoken directly from the cookie and then update the stale cookie using javascript.
var fresh_token = document.cookie.match('csrftoken=([a-zA-Z0-9]{32})

Resources