rails - "WARNING: Can't verify CSRF token authenticity" for json devise requests - ruby-on-rails-3

How can I retrieve the CSRF token to pass with a JSON request?
I know that for security reasons Rails is checking the CSRF token on all the request types (including JSON/XML).
I could put in my controller skip_before_filter :verify_authenticity_token, but I would lose the CRSF protection (not advisable :-) ).
This similar (still not accepted) answer suggests to
Retrieve the token with <%= form_authenticity_token %>
The question is how? Do I need to do a first call to any of my pages to retrieve the token and then do my real authentication with Devise? Or it is an information one-off that I can get from my server and then use consistently (until I manually change it on the server itself)?

EDIT:
In Rails 4 I now use what #genkilabs suggests in the comment below:
protect_from_forgery with: :null_session, if: Proc.new { |c| c.request.format == 'application/json' }
Which, instead of completely turning off the built in security, kills off any session that might exist when something hits the server without the CSRF token.
skip_before_filter :verify_authenticity_token, :if => Proc.new { |c| c.request.format == 'application/json' }
This would turn off the CSRF check for json posts/puts that have properly been marked as such.
For example, in iOS setting the following to your NSURLRequest where "parameters" are your parameters:
[request setHTTPMethod:#"POST"];
[request setValue:#"application/json"
forHTTPHeaderField:#"content-type"];
[request setValue:#"application/json"
forHTTPHeaderField:#"accept"];
[request setHTTPBody:[NSData dataWithBytes:[parameters UTF8String]
length:[parameters length]]];

You can send the CSRF token, after a successful log-in, using a custom header.
E.g, put this in your sessions#create :
response.headers['X-CSRF-Token'] = form_authenticity_token
Sample log-in response header providing the CSRF token:
HTTP/1.1 200 OK
Cache-Control: max-age=0, private, must-revalidate
Connection: Keep-Alive
Content-Length: 35
Content-Type: application/json; charset=utf-8
Date: Mon, 22 Oct 2012 11:39:04 GMT
Etag: "9d719d3b9aabd413c3603e04e8a3933d"
Server: WEBrick/1.3.1 (Ruby/1.9.3/2012-10-12)
Set-Cookie: [cut for readability]
X-Csrf-Token: PbtMPfrszxH6QfRcWJCCyRo7BlxJUPU7HqC2uz2tKGw=
X-Request-Id: 178746992d7aca928c876818fcdd4c96
X-Runtime: 0.169792
X-Ua-Compatible: IE=Edge
This Token is valid until you log-in again or (log-out if you support this through your API).
Your client can extract and store the token from the log-in response headers. Then, each POST/PUT/DELETE request must set the X-CSRF-Token header with the value received at the log-in time.
Sample POST headers with the CSRF token:
POST /api/report HTTP/1.1
Accept: application/json
Accept-Encoding: gzip, deflate, compress
Content-Type: application/json; charset=utf-8
Cookie: [cut for readability]
Host: localhost:3000
User-Agent: HTTPie/0.3.0
X-CSRF-Token: PbtMPfrszxH6QfRcWJCCyRo7BlxJUPU7HqC2uz2tKGw=
Documentation: form_authenticity_token

Indeed simplest way. Don't bother with changing the headers.
Make sure you have:
<%= csrf_meta_tag %>
in your layouts/application.html.erb
Just do a hidden input field like so:
<input name="authenticity_token"
type="hidden"
value="<%= form_authenticity_token %>"/>
Or if you want a jquery ajax post:
$.ajax({
type: 'POST',
url: "<%= someregistration_path %>",
data: { "firstname": "text_data_1", "last_name": "text_data2", "authenticity_token": "<%= form_authenticity_token %>" },
error: function( xhr ){
alert("ERROR ON SUBMIT");
},
success: function( data ){
//data response can contain what we want here...
console.log("SUCCESS, data="+data);
}
});
Basically when you post your json data just add a valid authenticity_token field to the post data and the warning should go away...

I resolved that error this way:
class ApplicationController < ActionController::Base
protect_from_forgery
skip_before_action :verify_authenticity_token, if: :json_request?
protected
def json_request?
request.format.json?
end
end
Source:
http://api.rubyonrails.org/classes/ActionController/RequestForgeryProtection.html

What's worrying is that in Rails 3.2.3 we now get the CSRF warning in production.log but the post does not fail! I want it to fail as it protects me from attacks. And you can add the csrf token with jquery before filter btw:
http://jasoncodes.com/posts/rails-csrf-vulnerability

I have used the below. Using include? so if the content type is application/json;charset=utf-8 then it is still working.
protect_from_forgery with: :null_session, if: Proc.new { |c| c.request.format.include? 'application/json' }

This answer is better.
You get to keep the CSRF-TOKEN validation with no extra effort (the token is appended) before any XMLHttpRequest send. No JQuery, no nothing just copy/paste and refresh.
Simply add this code.
(function() {
var send = XMLHttpRequest.prototype.send,
token = $('meta[name=csrf-token]').attr('content');
XMLHttpRequest.prototype.send = function(data) {
this.setRequestHeader('X-CSRF-Token', token);
return send.apply(this, arguments);
};
}());

I had the same issue with the following version of Rails:
gem 'rails', :git => 'git://github.com/rails/rails.git', :branch => '3-2-stable'
I updated to 3.2.2 and everything works fine for me now. :)
gem 'rails', '3.2.2'

I ran into the same issue tonight.
The reason that happens is because when you sign in the last csrf-token is no longer valid.
What I did was:
$("meta[name=csrf-token]").attr('content', '<%= form_authenticity_token %>'); in your app/views/devise/sessions/create.js.rb.
Now it does have a valid csrf-token :)
I hope it helps

Also for development/test mode.
protect_from_forgery with: :exception unless %w(development test).include? Rails.env
This warning shows because you are using :null_session, in Rails 4.1 it works by default if no with: options specified.
protect_from_forgery

Related

ASP.NET Core Setting Response.ContentType via IActionFilter results in 406 Not allowed

I'm using IActionFilter.OnActionExecuted (ASP.NET Core 3.1) to set context.HttpContext.Response.ContentType with a media type and charset, e.g. application/json; charset=utf-8.
However when setting the ContentType property the response status code 406 Not allowed is returned.
I've tried:
Setting the Accept headers of the request (Accept: application/json; Accept-Charset: utf-8);
services.AddMvc(options => options.RespectBrowserAcceptHeader = false);
services.AddMvc(options => options.ReturnHttpNotAcceptable = true);
plus combinations of the above, but the 406 keeps coming back.
Then I tried to set the response content type without the charset and this worked!!
This makes sense probably as Accept=application/json != Content-Type=application/json; charset=utf-8, however I can't find a way to set the charset now.
Any thoughts?
Cheers,
Peter

How can I add custom header parameters to a API GET call in clojurescript

I'm building a demo application in clojurescript with KeeFrame and to retrieve a part of the information for this website I need to call an external API which requires a custom HTTP header parameter in the GET requests
I'm using re-frame.core for the API calls, which uses ajax.core.
I also tried to replace this with cljs-http.client. However the result is the same.
I already managed to add custom header parameters to the request header by using clj-http at server site. But this is not a solution I want to implement for this website because that means that I first have to rebuild the API I'm calling. So I can use it from my clojurescript without the parameter.
This code works. A correct GET request is generated
{:http-xhrio {
:method :get
:uri (str transuri "/acquirer/" 673072009 "/acquirerref/" acquirerRefNo)
:headers {"Accept" "application/json"}
:response-format (http/json-response-format {:keywords? true})
:on-failure [:common/set-error]}}
With "Accept: application/json" as a request header
This code does not work. Instead of a GET request an OPTIONS request is generated
{:http-xhrio {
:method :get
:uri (str transuri "/acquirer/" 673072009 "/acquirerref/" acquirerRefNo)
:headers {"Accept" "application/json" "Custom" "Value"}
:response-format (http/json-response-format {:keywords? true})
:on-failure [:common/set-error]}}
And in the request header "Accept: application/json" is not visible but "Access-Control-Request-Headers: custom" is
I expected a GET request with "Accept: application/json" and "Custom: Value" in the request header.
Can someone tell me what I'm doing wrong or provide me with a link with information about this?
Thanks in advance
The Browser will send a "preflight" OPTIONS request to verify that it is allowed to send the "Custom" request header. The server is supposed to approve by replying with "Access-Control-Allow-Headers".
See https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS
I have not used KeeFrame, but I have a working example using a new re-frame lib I'm working on. It invokes an ajax request using this interceptor:
(def ajax-intc
"Interceptor for performing AJAX requests"
(interceptor
{:id :ajax-intc
:enter identity
:leave (fn [ctx] ; #todo (with-result ctx ...)
(let [ajax (:ajax ctx)]
;(t/spyx :ajax-intc-start ctx)
;(t/spyx :ajax-intc-start ajax)
(when-not (nil? ajax)
(t/spy :awt-ajax-intc--ajax ajax)
(let [method (t/grab :method ajax)
uri (t/grab :uri ajax)
ajax-opts-present (set/intersection (set (keys ajax)) ajax-options-keys)
opts-map (t/submap-by-keys ajax ajax-opts-present)]
;(t/spy :ajax-intc-ready (t/vals->map method uri opts-map))
(condp = method
:get (do
(t/spy :awt-ajax-intc--opts-map opts-map)
(ajax/GET uri opts-map))
:put (ajax/PUT uri opts-map)
:post (ajax/POST uri opts-map)
(throw (ex-info "ajax-intc: unrecognized :method" ajax))))))
ctx)}))
When invoked with this event:
(flame/dispatch-event [:ajax-demo :get "/fox.txt"
{:handler ajax-handler
:error-handler ajax-error-handler
:headers {"custom" "something"}
}])
one can see in the Chrome dev console that the headers come through:
:awt-localstore-load-intc--loaded-value-1 {}
core.cljs:192 :awt-ajax-intc--ajax => {:method :get, :uri "/fox.txt", :handler #object[flintstones$core$ajax_handler], :error-handler #object[flintstones$core$ajax_error_handler], :headers {"custom" "something"}}
core.cljs:192 :awt-ajax-intc--opts-map => {:handler #object[flintstones$core$ajax_handler], :error-handler #object[flintstones$core$ajax_error_handler], :headers {"custom" "something"}}
If you want to try it out, you can clone this repo: git#github.com:cloojure/cljs-enflame.git
and then run:
lein clean
lein figwheel
and see it run in the browser.

Ionic CORS Error, But Server Has CORS Enabled

I have an Ionic 4 app that uses a lambda API hosted on AWS. CORS is enabled on the API Gateway. The following snippet is from a curl request to the API.
< content-type: application/json
< content-length: 42
< date: Sat, 16 Feb 2019 02:19:25 GMT
< x-amzn-requestid: 47a5fcac-3191-11e9-af42-d387861aa6ad
< access-control-allow-origin: *
< access-control-allow-headers: Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token
< x-amz-apigw-id: VK7vFGc4oAMFTqg=
< access-control-allow-methods: POST,OPTIONS
This post discusses a few possible workarounds (change content type, etc.), but they don't work.
Changing the Content-Type header to text/plain or removing that header altogether makes no difference.
The following error is also presented on the Ionic console
Cross-Origin Read Blocking (CORB) blocked cross-origin response
https://mycoolapi.com/GetLegal with MIME type application/json.
See https://www.chromestatus.com/feature/5629709824032768 for more details.
The following is my service code.
getLegal(data: any) {
return new Promise((resolve, reject) => {
let httpHeaders = new HttpHeaders().set('Content-Type', 'application/json');
this.httpClient.post(this.apiUrl+'/GetLegal', JSON.stringify(data), {
headers: httpHeaders,
})
.subscribe(res => {
resolve(new LegalResponse(res));
}, (err) => {
console.log("Oops, there has been an error")
reject(err);
});
});
}
Help?
This ended up being a bug on the Amazon side. The curl snippet was from a GET method, which was sending the CORS headers. The POST method was not. After redeploying the API without changing anything, the GET method was no longer sending the CORS headers and the POST method was. The application is working, for now.

Bing Image Search v5.0 returns error

As of today (September 13, 2017) did Bing Image Search v 5.0 change their backend API?
Using this console, adding the header "Content-Type" with a value of "multipart/form-data" gives a response with expected values.
However, when called from my iOS app with the exact same parameters and headers, I receive an error response of "RequestParameterInvalidValue;
message = "Parameter has invalid value.";
parameter = imgurl;"
The following code worked as of yesterday:
NSString* path = #"https://api.cognitive.microsoft.com/bing/v5.0/images/search";
NSString* skip = [NSString stringWithFormat:#"skip=%li", (long)searchOffset];
NSString* queryString = [NSString stringWithFormat:#"q=%#", searchQuery];
NSArray* array = #[
// Request parameters
#"entities=true",
#"count=50",
skip,
#"safeSearch=Strict",
queryString
];
NSString* string = [array componentsJoinedByString:#"&"];
path = [path stringByAppendingFormat:#"?%#", string];
NSLog(#"%#", path);
NSMutableURLRequest* _request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:path]];
[_request setHTTPMethod:#"POST"];
// Request headers
[_request setValue:#"multipart/form-data" forHTTPHeaderField:#"Content-Type"];
[_request setValue:accessKey forHTTPHeaderField:#"Ocp-Apim-Subscription-Key"];
// Request body
[_request setHTTPBody:[path dataUsingEncoding:NSUTF8StringEncoding]];
I can remove the Content-Type header from the code in Test and get a working response, but this worked yesterday on our Production app. Is there a workaround to get my production code working again without having to resubmit the App for approval?
There is a fix in deployment now that should fix issue with POST requests.
Also had this error right now. Tried to debug it and this is what my dump returned:
HTTP_Request2_Response {#460 ▼
#version: "1.1"
#code: 400
#reasonPhrase: "Bad Request"
#effectiveUrl:
"https://api.cognitive.microsoft.com/bing/v5.0/images/search?q=cats"
#headers: array:16 [▼
"cache-control" => "no-cache, no-store, must-revalidate"
"pragma" => "no-cache"
"content-length" => "169"
"content-type" => "application/json; charset=utf-8"
"expires" => "-1"
"vary" => "Accept-Encoding"
"server" => "Microsoft-HTTPAPI/2.0"
"p3p" => "CP="NON UNI COM NAV STA LOC CURa DEVa PSAa PSDa OUR IND""
"bingapis-traceid" => "18AAD6F029D1439EB653971FBD07B6EF"
"x-msedge-clientid" => "260206484A6764FE375C0CB54B826518"
"x-msapi-userstate" => "d3dd"
"x-msedge-ref" => "Ref A: 18AAD6F029D1439EB653971FBD07B6EF Ref B:
SG2EDGE0713 Ref C: 2017-09-14T07:12:07Z"
"apim-request-id" => "9de68803-fee7-4a56-aa0e-f00e2b43929b"
"strict-transport-security" => "max-age=31536000; includeSubDomains;
preload"
"x-content-type-options" => "nosniff"
"date" => "Thu, 14 Sep 2017 07:12:06 GMT"
]
#cookies: []
#lastHeader: "date"
#body: "{"_type": "ErrorResponse", "instrumentation": {}, "errors":
[{"code": "RequestParameterInvalidValue", "message": "Parameter has
invalid value.", "parameter": "i ▶"
#bodyEncoded: true
I thought there was something wrong with my code but then I saw your thread here and concluded it was an error on Bing/Azure's end.
The following curl works. However, yesterday I completely redid my Microsoft cognitive keys in all categories because they became broken. I thought the keys probably broke months ago but maybe it was just yesterday. I did not have to change any parameters in any of my many test files.
curl "https://api.cognitive.microsoft.com/bing/v5.0/images/search?q=cats" -H "Content-Type: multipart/form-data" -H "Ocp-Apim-Subscription-Key: <My Bing Search API Key 1>"

Devise Token Auth / Angular2-Token, update password, Completed 401 Unauthorized

I'm having troubles restoring password with devise_token_auth. and Angular2-Token. I'm successfully receiving the email with the link to update my password. But I'm getting an 401 Unauthorized response when submiting the new password.
Front end. I'm getting the token from the URL with urlParams.get('token')
onPasswordUpdate() {
let token = this.urlParams.get('token');
var obj = Object.assign(this._updatePasswordData, { reset_password_token: token })
this._tokenService.patch('auth/password/', obj ).subscribe(
res => res,
error => error
);
}
Back end response.
Started PATCH "/api/auth/password/" for 127.0.0.1 at 2016-12-01 21:17:48 +0100
Processing by DeviseTokenAuth::PasswordsController#update as JSON
Parameters: {"reset_password_token"=>"[FILTERED]", "password"=>"[FILTERED]", "password_confirmation"=>"[FILTERED]"}
Completed 401 Unauthorized in 1ms (Views: 0.4ms | ActiveRecord: 0.0ms)
In the link of the email I get the following token : reset_password_token=HneZDoKTMCLF3_SLfnxy
When I visit the link, the user record gets updated with the following attributes :
reset_password_token: "aa3cba76c7b1d8f78cde6856f43e1cce57f5fc8e5301842733de677eff909bc1"
tokens: {}
Then in the browser URL I get the following token=agejaip2SqOp9nvwE1GAHQ&uid
And then the user record get updated with the following attribues :
...
reset_password_token: "HneZDoKTMCLF3_SLfnxy",
tokens: {"pv9i1BDTM29ezep0KSPzpA"=>{"token"=>"$2a$10$cS9gbe9UBICcgphZHRAENOMS6NlEe0Em1cNufY3LSRTPE.hRMabvi", "expiry"=>1481834221}}
...
It seems to me that the token I get back in URL is not correct.
Those anyone have an idea ?
Sorry It's a bit hard to explain.
Many thanks.
rails (4.2.4)
devise_token_auth (0.1.34)
devise (= 3.5.1)
angular2-token: 0.2.0-beta.1
I faced similar challenges recently, and this was how I solved it.
Expose the 'access-token', 'expiry', 'token-type', 'uid', 'client' for your backend. Check here and here
config.middleware.use Rack::Cors do
allow do
origins '*'
resource '*',
:headers => :any,
:expose => ['access-token', 'expiry', 'token-type', 'uid', 'client'],
:methods => => [:get, :post, :options, :delete, :put, :patch]
end
end
Set your redirect_url of path: /password, method: POST. Check info here
We need to modify the reset_password_instructions.html.erb to point it to the api GET /auth/password/edit. More information provided here.
E.g. if your API is under the api namespaces:
<%= link_to 'Change my password', edit_api_user_password_url(reset_password_token: #token, config: message['client-config'].to_s, redirect_url: message['redirect-url'].to_s) %>