ASP.NET MVC Ajax form submitted cross domain not sending Cookies - asp.net-mvc

I have a site that has some forms that use ASP.NET MVC Ajax. An example of BeginForm method:
#using (Ajax.BeginForm("HandleSignin", "Profile", null, new AjaxOptions() {
HttpMethod = "POST",
Url = Url.Action("HandleSignin", "Profile", null, Request.Url.Scheme),
OnBegin = "SetWithCredentialsTrue(xhr)",
InsertionMode = InsertionMode.Replace,
UpdateTargetId = "signin-form-container" },
new { id = "sign-in-form", #class = "text-left-desktop group" }))
{
#Html.AntiForgeryToken()
#Html.TextBoxFor(x => Model.Email, new { placeholder = "Email" })
#Html.PasswordFor(x => Model.Password, new { placeholder = "Password" })
<input type="submit" value="SignIn" class="button small-button">
}
Note that because of the Request.Url.Scheme param in the of the Url.Action method, the URL is being set to a different domain than the domain that the browser is getting this from. This is done because the main site is hosted statically using a CDN while the form is loaded from another domain using AJAX. This works, except that the cookies are not sent in the AJAX request. I tried to have the cookies sent by setting xhr.withCredentials = true by using the OnBegin event and this JavaScript:
<script type="text/javascript">
function SetWithCredentialsTrue(xhr) {
console.log("SetWithCredentialsTrue(xhr)", xhr);
xhr.withCredentials = true;
}
</script>
While I can see that SetWithCredentialsTrue() method gets called, it does not seem to work in that the HTTP Request generated when the form is submitted does not have the Cookie header.
All of the server-side handlers are setting the Access-Control-Allow-Credentials response header to true and the Access-Control-Allow-Origin to the main (static) site domain.
UPDATE: With some more console logging, I have verified that the xhr parameter passed to my OnBegin event handler (SetWithCredentialsTrue) is NOT an XMLHttpRequest object and hence setting withCredentials on it does not have an affect. So the question is how can I access the XMLHttpRequest object?

I finally figured this out. The XMLHttpRequest object is not exposed via the ASP.NET MVC library. I was able to alter jquery.unobtrusive-ajax.js, the JS library used by the ASP.NET MVC helper so that it sets withCredentials to true:
$(document).on("submit", "form[data-ajax=true]", function (evt) {
var clickInfo = $(this).data(data_click) || [],
clickTarget = $(this).data(data_target),
isCancel = clickTarget && clickTarget.hasClass("cancel");
evt.preventDefault();
if (!isCancel && !validate(this)) {
return;
}
asyncRequest(this, {
url: this.action,
type: this.method || "GET",
data: clickInfo.concat($(this).serializeArray()),
xhrFields: {
withCredentials: true
}
});
});
Note: xhrFields is the part that I added.

as per my understanding what you trying to do is, you want to send data from one domain to another domain using AJAX post right?
If it is true then, i want to tell you that browser doesn't allow you to do that because of security issue.
If you still want to do that then you have two option's by using CORS and JSONP.
http://csharp-video-tutorials.blogspot.in/2016/09/calling-aspnet-web-api-service-in-cross.html
http://csharp-video-tutorials.blogspot.in/2016/09/cross-origin-resource-sharing-aspnet.html

Related

How can I verify authenticity token when submitting form through ajax success?

I have a condition in which firstly the ajax request is triggered to fetch respective host url. In case the condition returned is true I want to submit the rails form. The form is submitted correctly but I get the error can't verify authenticity token.
$(document).ready(function() {
var url, email;
$("form#new_user").submit(function(e){
e.preventDefault();
email = document.getElementById("user_email").value;
$.ajax("/return_host", {
type: "GET",
data: {
email: email
},
beforeSend: function(xhr) {
xhr.setRequestHeader('X-CSRF-Token', $('meta[name="csrf-token"]').attr('content'));
},
success: function(data) {
if(data['goto'] == true){
host = data["host"];
url = host + "/users/sign_in";
$("form").attr("action", url);
$("form").trigger('submit.rails');
} else {
location.reload();
}
}
});
});
});
How can I send authenticity token when triggering rails form through ajax success?
Please check whether your form has a hidden input field named authenticity_token or not, for forms generated with rails helpers this field is included by default.
If not then this is what is causing error as rails expects a param called authenticity_token when you post a form.
You can add this on the fly in your success callback as shown below:
success: function(data) {
if(data['goto'] == true){
host = data["host"];
url = host + "/users/sign_in";
var form = $("form");
var authenticityToken = document.createElement("input");
authenticityToken.setAttribute("type", "hidden");
authenticityToken.setAttribute("name", "authenticity_token");
authenticityToken.setAttribute("value", $('meta[name="csrf-token"]').attr('content'));
form.append(authenticityToken);
form.attr("action", url);
form.trigger('submit.rails');
} else {
location.reload();
}
}
Hope it helps!
If you're on Rails 5.1+ I'd recommend using form_with helper to handle the ajax request [https://api.rubyonrails.org/v5.2.1/classes/ActionView/Helpers/FormHelper.html#method-i-form_with].
You'll still be able to hook into the success and error callbacks using code as I've shared below.
```
let form = document.getElementById('my_form')
form.addEventListener('ajax:error', (event) => {
let form = event.target
let [errors, status, xhr] = event.detail
// DO SOMETHING
})
form.addEventListener('ajax:success', (event) => {
let form = event.target
// DO SOMETHING
})
form.addEventListener('ajax:before', (event) => {
let form = event.target
// DO SOMETHING
})
```
This way rails will handle the authenticity token/csrf yet you'll still be able to use Ajax - with option data: { remote: true } to ensure no page reload.
Hope this helps.

AngularJS + Rails: get a resource but stay on current page

I have a page with a button that will change the state of the resource on the server. Everything is wired up and working fine. But I had to resort to a server response that sent the browser "back" because I don't want to display a new template for the action url.
This seems like a typical thing to do: ask the server to do something, then get the current state of the resource and redisplay it in the current template without refetching the template.
The markup on the page is like this:
<a ng-href="/api/preference/{{video._id}}/add_to_watchlist" data-method="get">
<i rel="tooltip" data-original-title="Add to watchlist" class="icon-3x action-icon
icon-plus-sign" ng-class="{iconSelected : video.user_watchlist == 1}">
</i>
</a>
This highlights the icon depending on the value of $scope.video.watchlist. When clicked it fires a custom action on the server to add the video being viewed to the current user's watchlist. The url created is /api/preference/{{video._id}}/add_to_watchlist, this fires the server controller, which does the right thing but instead of going to a template for /api/preference/{{video._id}}/add_to_watchlist the server responds by telling the client to go "back" in Rails this is redirect_to :back.
Clearly this is wrong. It works but refetches the entire page markup and data.
The AngularJS controller that loads the original data is here:
GuideControllers.controller('VideoDetailCtrl', ['$scope', 'Video',
function($scope, Video) {
var pattern = new RegExp( ".*/([0-9,a-f]*)", "gi" );
//ugh, there must be a better way to get the id!
$scope.video = Video.get({ id: pattern.exec( document.URL )[1] });
}
]);
Here is the resource that gets the json
var VideoServices = angular.module('VideoServices', ['ngResource']);
VideoServices.factory('Video', ['$resource',
function($resource){
return $resource("/api/videos/:id", {id: "#id"}, {
update: {method: "PUT"},
query: {
isArray: true,
method: "GET",
headers: {
"Accept": "application/json",
"X-Requested-With": "XMLHttpRequest"
}
},
get: {
isArray: false,
method: "GET",
headers: {
"Accept": "application/json",
"X-Requested-With": "XMLHttpRequest"
}
}
});
}
]);
Somehow I need to
tell the server to add_to_watchlist without changing the browser URL
trigger a refetch of the video json without reloading the template.
It seems like you're relying on routing to handle a model update on the server, instead of doing it through angular's $resource service. Here's a quick and dirty:
Instead of routing, call a function when the user clicks:
<a ng-click="addToWatchlist(video._id)">
<i rel="tooltip" data-original-title="Add to watchlist" class="icon-3x action-icon
icon-plus-sign" ng-class="{iconSelected : video.user_watchlist == 1}">
</i>
</a>
Add the click handler function to your controller (I'm using $http here, but you would be better off adding a custom action to your Video resource). On the success callback you can reload the video. Or, better yet, you can return the video from the add_to_watchlist action.
Check out http://docs.angularjs.org/tutorial/step_07 which explains the $routeParams (to answer your comment about getting the video id).
GuideControllers.controller('VideoDetailCtrl', ['$scope', '$http', '$routeParams', 'Video',
function($scope, $http, $routeParams, Video) {
$scope.video = Video.get({ id: $routeParams.videoId });
$scope.addToWatchlist = function(videoId) {
$http.get('/api/preference/'+videoId+'/add_to_watchlist').success(function() {
$scope.video = Video.get({ id: $routeParams.videoId });
};
};
}
]);

page refresh unsetting Ember controller property

If I do a manual page refresh or set a redirect like this
}).then(function() {
document.location = "/";
}, function() {
in a function in my Ember app, an Ember controller property is becoming unset. I'm not sure if it matters which property is becoming unset, but in case it does, here's the code. In my application template, I check for whether a user is authenticated
{{#if isAuthenticated}}
blah blah
{{else}}
blah blah
{{/if}}
The property is set in the AuthController
App.AuthController = Ember.ObjectController.extend({
currentUser: null,
isAuthenticated: Em.computed.notEmpty("currentUser.email"),
login: function(route) {
var me;
me = this;
return $.ajax({
url: "/users/sign_in.json",
type: "POST",
data: {
"user[email]": route.currentModel.email,
"user[password]": route.currentModel.password
},
success: function(data) {
me.set('currentUser', data.user);
So, after I do a page refresh, my currentUser is (according to Ember) no longer showing as authenticated. However, on the (Ruby on Rails) server side the user is still authenticated. I put this code in the layout to check and after Ember thinks that I'm signed out (with a page refresh), my server code is still telling me I'm signed in (which in fact I am).
<% if current_user %>
there is a current user
<% end %>
Can you explain why this might be happening?
Your best is to set up is probably to make isAuthenticated a computed property that does an ajax GET to /users/current if currentUser is null. /users/current should return {} if the user is not authenticated, and the current user data if they are.
App.AuthController = Ember.ObjectController.extend({
currentUser: null,
isAuthenticated: function() {
var currentUser = this.get('currentUser');
if(Ember.isEmpty(currentUser)) {
$.ajax({
type: "GET",
url: "/users/current.json",
async: true
}).done( function( data ) {
me.set('currentUser', data.user);
});
return false;
} else {
return true;
}
}.property('currentUser'),
});
Why are you doing a manual page refresh? This way you are starting the browser completely from scratch and therefore the Ember App initializes again and therefore the property currentUser is not set on your Controller.
Instead of doing this:
}).then(function() {
document.location = "/";
}, function() {
I assume that you want to go to the start page of your app (since you want to go to "/"). Instead you should use the Ember feature of transitioning between routes. I do not know, where you execute the code above, but you can do this either from one of your controllers or in a route.
In a controller you could use transitionToRoute:
// index is a Route which is implicitly generated by Ember for you
this.transitionToRoute('index');
In a router you could use transitionTo:
this.transitionTo('index');

AJAX call to MVC controller action

I am new to MVC programming.I am working on a simple POC application that displays/edits data from Database.
I have 2 views and 2 controllers. On one of them the JQuery AJAX call to MVC controller action method is working fine. But not on the other one (In here, the AJAX call is not triggering the Action method).
Can anyone had this situation before???
The JQuery code is below:
$('#ddlZones').change(function () {
var value = $('#ddlZones option:selected').val();
// alert(value); var status = $('#ddlStatus option:selected').val();
// alert(status); $('#lstResources').children().remove();
$('#lstBeds').children().remove();
$.ajax({ url: 'FillResources', type: 'POST',
data: JSON.stringify({ ZoneId: value }),
dataType: 'json', contentType: 'application/json',
success: function (result) {
for (var r in result) {
$('#lstResources').append('<option value="' + result[r].Value + '">' + result[r].Text + '</option>');
}
}
});
});
Thanks
Latha
Check the controller url is called or not,
Also in data parameter you can call it simply like { ZoneId: value } instead of
JSON.stringify({ ZoneId: value }),
At server side get this parameter by using $_POST if you are using PHP
in controller, also check in console that, you are getting json from server side or not.
$.ajax({ url: 'FillResources', type: 'POST',
data:{ ZoneId: value },
dataType: 'json', contentType: 'application/json',
success: function (result) {
for (var r in result) {
$('#lstResources').append('<option value="' + result[r].Value + '">' + result[r].Text + '</option>');
}
}
});
Please check the request and the response in the browser. You can use the inbuilt ones in IE/Chrome/FF(Firebug) or fiddler. It probably is just a routing issue if this is ASP.NET MVC.
Specifically, look for what your request url is and what is the HTTP response from the server.
It is always a good practice to specify both the controller and actions in the routing urls to avoid any such errors.
Another thing to check will be if the change function is actually being called? You can put a console.log before the ajax request to make sure that this is not the case.

global ajaxError event

i have a global ajaxError event in my base.master, something like this
$(document).ajaxError(function(event, request, settings,thrownError) {
$("#results").append( "<li>some error msg.</li>" );
});
but i dont want to use "#results". i want it to be dynamic. i want the function to always display the error msg in the update-target element. how can i find the UpdateTarget Id that was used from my ajax call in the error event? thanks.
using (Ajax.BeginForm("action", null,
new AjaxOptions {
UpdateTargetId = "results", <--find this element in my error event
LoadingElementId = "loading",
I am not familiar with ASP, but the ajaxError receives all settings that are passed to the ajax method in the aptly named settings variable.
So if you have an AJAX call like this:
$.ajax({
url: '/some/url.asp',
data: myData,
resultsEle: $('#myResults')
});
You can access resultsEle in the ajaxError like this:
$(document).ajaxError(function(event, request, settings, thrownError) {
settings.resultsEle.append( "<li>some error msg.</li>" );
});
Again, I am not familiar with the way you are calling the AJAX method in your ASP code, but my guess would be you can get to the element this way:
$(document).ajaxError(function(event, request, settings, thrownError) {
$('#'+settings.UpdateTargetId).append( "<li>some error msg.</li>" );
});

Resources