InvalidAuthenticityToken, Ajax, Rails 5 with Device - ruby-on-rails

I am using Ajax authentication with Rails 5 and Devise. The table users has column blocked and if the user is blocked he will be logged out. I have set header for Ajax globally:
$(document).ajaxSend(function(e, xhr, options) {
var token = $("meta[name='csrf-token']").attr("content");
xhr.setRequestHeader("X-CSRF-Token", token);
});
This is the replaced create method in SessionsController:
def create
if warden.authenticate(:scope => resource_name)
if current_user.blocked
sign_out(#user)
return render json: {blocked: true}
else
return render json:{success: true}
end
else
return render json: {error: true}
end
end
If the user is blocked and he tries to authenticate the response from the server is {blocked: true}. Without refreshing the page, if he tries again the response is an error :
ActionController::InvalidAuthenticityToken in Users::SessionsController#create
ActionController::InvalidAuthenticityToken
Extracted source (around line #195):
I see in the headers that the token is send every time when user tries to login.
I know that may be the problem is because after first time login the token is changed, but because the request is with Ajax it can get the new token, but I don't know how to fix that.

You can use ajaxComplete event upon ajax completion and set meta[name='csrf-token'] content upon after login request complete.

Related

Rails use of httparty to parse the JSON to external URL

In a Rails application, a user has to be redirected to an external site for external processing. The controller generates a valid JSON string and a set of headers:
#result = HTTParty.post(
"https://some-test.supertester.com/checkout",
:verify => false,
:body => JSON.parse(#payment).to_json,
headers: {
'Content-Type' => 'application/json',
'X-TimeStamp' => "#{Time.now.to_i}",
'X-API-ID:' => "#{#api_id}",
'X-API-Signature:' => "#{#api_signature}"
},
timeout: 20
)
This action is launched by the user via:
<%= link_to t('proceed'), data_url_cart_path(id: #existing_cart.id), class: 'button' %>
whose controller action generates the above JSON string and call.
However, Rails tries to respond to this action with a view with the same action's name (even generating a blank with format.json { head :no_content }), when the goal is to redirect the user to the aforementioned URL with the defined headers and payload.
How can this be achieved?
HTTParty is making a network request with a json payload. In this situation #response will be the response from the network request. If you call puts #response.parsed_response.inspect you will see the json returned with said response in the terminal window running your Rails server.
I think you may not need to redirect the user to an external site at all, but instead check the response to see if the action you were trying to make was successful and handle it in your own application.

errror 422 after making api call from react to rails

I have a RoR api and I'm building a react with redux page. I'm calling my user's todos.
This is how it works so far: When it sign up, i store the token key in the state, and then use that to make an api call to get my todos (index).
I have my action here:
def index
#todos = current_user.todos
json_response(#todos)
end
This is the block that is failing:
loginHandler() {
const { email, password } = this.state;
const { login, tokenize, token } = this.props;
axios.post('http://localhost:3001/auth/login', {withCredentials: true,
email: email, password: password})
.then(response => {
login(true);
tokenize(response.data.auth_token);
axios.get('http://localhost:3001/todos', {Authorization: token}) // Error occurs
.then(response => {
console.log(response);
}).catch(error => console.log(error));
}).catch(error => console.log(error));
}
Where tokenize and login are actions passed to redux. The error is in the second axios call, where I'm getting a 422 Unprocessable Entity.
I made a manual test with httpie from my terminal and there, it works.
Also debug my todos#index action and this is the error from the server-side:
AbstractController::DoubleRenderError (Render and/or redirect were called multiple times in this action. Please note that you may only call render OR redirect, and at most once per action. Also note that neither redirect nor render terminate execution of the action, so if you want to exit an action after redirecting, you need to do something like "redirect_to(...) and return".).
My RoR is 5.2 and ruby 2.6.3.
The login works, the signup works, but the get method for todos is not working ONLY in react. In httpie from my terminal it does work. I've been fighting for a while with this one and no resource online has been really helpful, which made me think there's some typo there, that I haven't seen.
Authorization should be sent in headers:
axios.get('http://localhost:3001/todos', { headers : { Authorization: token } })
https://flaviocopes.com/axios-send-authorization-header/

How to execute Angular $http.post to rails controller followed by redirect?

I would like a method on a button to execute a post redirect, and then have the page redirected by rails according to the controller's action.
Now I have the following method attached to a button on an angular page running in the page's Angular controller:
$scope.addClass = function(class_id_string) {
let payload = {class_id: class_id_string};
$http({
method: 'POST',
url: ADD_CLASS_ACCOUNT_URL,
data: payload
});
}
In my Rails controller, I have the following code:
def save_class_account
class_id = params[:class_id]
return redirect_to display_chart_path unless class_id.nil?
[...]
end
I have confirmed that the controller method is being called, and that the class_id parameter is coming through in the params collection. But still, nothing happens on the page when this method is called. I assume what is actually happening is that the entire page is being returned to the $http post method and ignored, instead of redirecting the browser, but I am not sure what is really going on here. But the page doesn't redirect as desired.
How can I get the redirect to happen?
Angular is using Ajax calls using $http service. It means update a web page/Send data to server without reloading the page.
When you post data using HTTP Service, it will call Rails Controller methods and do the action and send the response back to Ajax.
Here page redirection doesn't work from Rails Controller. If you want to redirect page, Please do as shown below in the $http service itself.
$scope.addClass = function(class_id_string) {
let payload = {class_id: class_id_string};
$http({
method: 'POST',
url: ADD_CLASS_ACCOUNT_URL,
data: payload
}).then(function mySuccess(response) {
window.location = "Paste Redirection URL here"
}, function myError(response) {
"Show Error message here"
});
}
And also in the Rails Controller, When you call methods from Ajax, It will send JSON response as shown below
def save_class_account
class_id = params[:class_id]
render json: {url: display_chart_path, mgs: 'Successfully created'}, status: :created}
end

How to validate my controller action was called from within my Rails app

I have a simple user registration form.
When the user fills out the form and clicks "Submit" I have a JavaScript event that intercepts the submit and uses AJAX to call the validate method below to check the form data before submitting. If the form data is ok it continues with the submit, but if it's not it cancels the Submit and displays a warning on the page. It's a simple way of doing real-time client side validations.
# app/controllers/users_controller.rb
# Validates the form data. Returns an error message response in the format
# { response: "Invalid email format!" }
#
# A blank response means success
#
def validate
if request.xhr? || <request came from another method in this controller>
# Run various validations on input data and determine a response
# response_text = ...
render json: { response: response_text }
else
redirect_to root_path
end
end
def create
if JSON.parse(validate)["response"].blank?
User.create(...)
# Other stuff
end
end
However when the submit does eventually pass and go through, it sends a POST to the create action to create a new User. That method calls validate again (the cost of re-validating is minimal) to ensure that no one bypassed the form and submitted a malicious request directly to the server.
So my validate method has to respond to both (a) AJAX calls and (b) "internal" calls from within the app. All other calls to the validate action should just redirect to the root path.
I can tell whether the call was an a AJAX call pretty simple using request.xhr?.
How do I check whether the action was called internally and not by a user?
Taking a step back, is this a good general approach for validation? Any thoughts to improve on it?
Thanks!
Rails generates an authenticity token whenever a user views a form and stores it as a random string in the HTML. That token is also stored in the session and is therefore invisible to the user. Upon receiving a request, your application will compare the tokens to verify whether the request was generated from your form.
TL;DR: Don't worry, you're already protected.
I don't have a specific answer, but do have some information which could help. I'll gladly delete this if required...
AJAX calls and "internal" calls
You have to understand that XHR requests can only come from your app -- CORS (cross origin resource sharing).
Ajax calls can only come from your own domain, so don't think a malicious hacker could run some scraper or whatever -- you choose which domains are permitted to send XHR requests.
So when you're calling...
if request.xhr?
... as part of a validation, you need to scope when XHR will be used.
On the same note, what validation are you performing?
If you're validating input data, Rails handles this at the model layer. We've done something similar (click login/register at top):
The base structure of Rails is MVC, which means that your controller is only responsible for taking a request & building the appropriate data objects out of it.
Thus, if you're "validating" an input, what is there to validate apart from the data itself (handled by the model)?
As #MarsAtomic specified, too, Rails uses a CSRF token to provide some level of authentication for a form submission.
--
You could easily use the following:
#app/controllers/users_controller.rb
class UsersController < ApplicationController
respond_to :js, :json, :html
def create
#user = User.new user_params
respond_with #user.save
end
end
If you then sent the request from your front-end as json, you'd get back the created user OR the errors:
#app/views/users/new.html.erb
<%= form_for #user, remote: true, format: :json do |f| %>
<%= f.text_field .... %>
<%= f.submit %>
<% end %>
#app/assets/javascripts/application.js
$(document).on("ajax:error", "#new_user", function(xhr, status, error) {
// do stuff with response
});
In your create method, you want to return a #user if it's not valid (if they skip the ajax validate).
If that's the case, you don't want to do User.create, instead, create an instance and try to save it. If you do all this, you're doing the normal controller action for create and there is no need to call validate internally.
def create
#user = User.new(user_params)
if #user.save
# Other stuff
end
end
And, it's not a good practice to create a json response and parse it to see if an object is valid. Instead, either test with valid? or just try to save and get back a true or false.
In other words - keep your validate method separate from your create method. They'll both rely on valid? eventually.

How to implement redirect response with an AJAX request in Rails 3?

I have a simple scenario where I want to request a page. The request format is AJAX. If there is some error in the controller/action logic for that request, I want to redirect to an error page. The issue is that a redirect is not a JavaScript response type, so I am not sure whether it will work.
If there are no errors, then I want the page to be updated via the appropriate JavaScript response type.
What is best practice to achieve redirect responses given that the request format is AJAX?
This blog post enlightened me on what I think is the right way to do this, if your ajax response is ajax; at least, in the unobtrusive javascript paradigm. In essense, the ajax call always returns a standard json package, which can be parsed for information payload or a redirect url.
You can also put this in your ApplicationController to redirect properly for AJAX requests:
class ApplicationController < ActionController::Base
# Allows redirecting for AJAX calls as well as normal calls
def redirect_to(options = {}, response_status = {})
if request.xhr?
render(:update) {|page| page.redirect_to(options)}
else
super(options, response_status)
end
end
end
If you use jQuery, you can use .ajaxError()
$(document).ajaxError(function(event, request, settings){
location.href = '/error.html';
});
or assume you do a ajax post
var jqxhr = $.post("example.php", function() {
// Do what it completed will do
})
.error(function() {
location.href = '/error.html';
})

Resources