Reputation: 1319
I'm trying to give users on my website "points" or "credits" for tweeting about out the brand name.
I have the fancy twitter widget on the appropriate view...
<p><a href="https://twitter.com/share" class="twitter-share-button" data-text="Check Out This Awesome Website Yay" data-via="BrandName" data-hashtags="ProductName">Tweet</a>
<div id="credited"></div>
<script>window.twttr = (function (d, s, id) {
var t, js, fjs = d.getElementsByTagName(s)[0];
if (d.getElementById(id)) return;
js = d.createElement(s); js.id = id;
js.src= "https://platform.twitter.com/widgets.js";
fjs.parentNode.insertBefore(js, fjs);
return window.twttr || (t = { _e: [], ready: function (f) { t._e.push(f) } });
}(document, "script", "twitter-wjs"));
</script>
I have the JS all written up and pretty....
function creditTweet() {
$.post(
"/credit_tweet",
{},
function(result) {
var text;
if (result.status === "noop") {
text = "Thanks for sharing already!";
} else if (result.status === "ok") {
text = "5 Kredit Added";
}
$("#credited").html(text);
}
);
}
$(function() {
twttr.ready(function (twttr) {
window.twttr.events.bind('tweet', creditTweet);
});
});
Now the problem is either in the controller OR in the routes (where I'm posting). I think the routes are fine because the POST is almost working, because this is the description of the error on wikipedia - "422 Unprocessable Entity (WebDAV; RFC 4918) The request was well-formed but was unable to be followed due to semantic errors."
So, do you guys see anything wrong with my ruby code in the controller?
class SocialKreditController < ApplicationController
TWEET_CREDIT_AMOUNT = 5
def credit_tweet
if !signed_in?
render json: { status: :error }
elsif current_user.tweet_credited
Rails.logger.info "Not crediting #{ current_user.id }"
render json: { status: :noop }
else
Rails.logger.info "Crediting #{ current_user.id }"
current_user.update_attributes tweet_credited: true
current_user.add_points TWEET_CREDIT_AMOUNT
render json: { status: :ok }
end
end
end
And in my routes.rb, it's pretty straight forward, so I doubt there's anything wrong here...
get 'social_kredit/credit_tweet'
post '/credit_tweet' => 'social_kredit#credit_tweet'
Where oh where is this error? I clearly don't know smack about HTTP requests.
Upvotes: 34
Views: 98277
Reputation: 9105
a 422 can happen when updating a model in a POST request:
e.g. @user.update!(email: nil)
in users#update
will make a 422 if there is a validation on email like validates :email, presence: true
Upvotes: 1
Reputation: 29008
I had this challenge when working on a Rails 6 API-only application.
I followed the answer here - Rails: How to implement protect_from_forgery in Rails API mode to implement protect_from_forgery in Rails API mode, but I was still having the Can't verify CSRF token authenticity
error followed by the 422 Unprocessable Entity
error when I send post requests from Postman:
Started POST "/api/v1/programs" for ::1 at 2021-02-23 18:42:49 +0100
Processing by Api::V1::ProgramsController#create as JSON
Parameters: {"program"=>{"name"=>"Undergraduate", "code"=>"UD", "affiliate_status"=>false, "motto"=>"Our motto is ...", "description"=>"This is a new program", "school_id"=>1}}
Can't verify CSRF token authenticity.
TRANSACTION (0.3ms) BEGIN
↳ app/controllers/api/v1/programs_controller.rb:27:in `create'
Baserecord::School Load (0.3ms) SELECT "schools".* FROM "schools" WHERE "schools"."id" = $1 LIMIT $2 [["id", 1], ["LIMIT", 1]]
↳ app/controllers/api/v1/programs_controller.rb:27:in `create'
TRANSACTION (0.4ms) ROLLBACK
↳ app/controllers/api/v1/programs_controller.rb:27:in `create'
Completed 422 Unprocessable Entity in 30ms (Views: 0.8ms | ActiveRecord: 6.9ms | Allocations: 13172)
Here's I solved it:
The issue was caused by a validation error from my models. A validation I added to the model for the controller I was calling was failing. I had to check the Body of the response returned by Postman after making the request to find the error:
{
"affiliate_status": [
"can't be blank"
]
}
Once I fixed the error following this answer - Rails: Validation fails for ActiveRecord in setting a random Boolean Attribute, everything worked fine afterward.
That's all.
I hope this helps
Upvotes: 1
Reputation: 13780
ihaztehcodez(who was last active in 2016 so it won't help nudging him to post an answer) mentions that the skip_before_action :verify_authenticity_token
technique is not so secure 'cos you lose forgery protection.
they mention that the best/secure/'better practise', solutions are mentioned here WARNING: Can't verify CSRF token authenticity rails
e.g.
$.ajaxSetup({
headers: {
'X-CSRF-Token': $('meta[name="csrf-token"]').attr('content')
}
});
or
$.ajax({ url: 'YOUR URL HERE',
type: 'POST',
beforeSend: function(xhr) {xhr.setRequestHeader('X-CSRF-Token', $('meta[name="csrf-token"]').attr('content'))},
data: 'someData=' + someData,
success: function(response) {
$('#someDiv').html(response);
}
});
or
putting this within an ajax request
headers: {
'X-Transaction': 'POST Example',
'X-CSRF-Token': $('meta[name="csrf-token"]').attr('content')
},
Upvotes: 6
Reputation: 5014
If you're including Rails meta data in the HTML header with <%= csrf_meta_tags %>
it'll generate the following.
<meta name="csrf-param" content="authenticity_token" />
<meta name="csrf-token" content="ihwlaOLL232ipKmWYaqbSZacpJegQqooJ+Cj9fLF2e02NTQw7P/MfQyRuzruCax2xYWtEHWsb/uqiiZP6NWH+Q==" />
You can pull the CRSF token from the meta data and pass it into your async request.
Using the native js fetch
method you can pass it in as a x-csrf-token
header.
This is a trimmed onSave handler for a React component that enhances a standard Rails form.
onSaveHandler = (event) => {
const data = "Foo Bar";
const metaCsrf = document.querySelector("meta[name='csrf-token']");
const csrfToken = metaCsrf.getAttribute('content');
fetch(`/posts/${this.props.post_id}`, {
method: "PUT",
body: JSON.stringify({
content: data
}),
headers: {
'x-csrf-token': csrfToken,
'content-type': 'application/json',
'accept': 'application/json'
},
}).then(res => {
console.log("Request complete! response:", res);
});
}
Forgery protection is a good idea. This way we stay secure and don't mess with our Rails configuration.
Using gem 'rails', '~> 5.0.5'
& "react": "^16.8.6",
Upvotes: 0
Reputation: 337
Same problem I faced. It sorts out after adding
skip_before_action :verify_authenticity_token
at the top of your controller where your JS is calling or sending data.
class UserController < ApplicationController
skip_before_action :verify_authenticity_token
def create
end
end
as shown in code snippet.
Upvotes: 2
Reputation: 1319
I got it working!
I added a...
skip_before_action :verify_authenticity_token
to the controller.
The issue was found when checking out the logs and seeing that the CSRF token could not be verified.
Upvotes: 76