Why does rails not recognize `Accept: application/json` in the request header? - ruby-on-rails

I have some frontend javascript that makes an asynchronous http request to my backend rails server. On the frontend I am not using XHR (I use axios, although that's not entirely relevant to the question).
In the request, I set the following to tell the server I'm sending JSON and to make sure I get JSON back:
const config = {
headers: {
"Content-Type": "application/json",
"Accept": "application/json"
}
};
In my backend Rails controller if inspect the request I can verify the Accept header:
> request.headers
"HTTP_ACCEPT"=>"application/json, text/plain, */*"
However ActionPack/Rails still does not respect that and defaults to the format being :html
> request.format
=> #<Mime::Type:0x00007fe223919f80 #hash=-1773238723920954657, #string="text/html", #symbol=:html, #synonyms=["application/xhtml+xml"]>
Why is that?
I know I can append .json to my request URL to "force" it to specify that format, but is that the only option? I can append it easily but it seems like an implementation specific to Rails and not really the "right" approach.
Additionally, the source code for the request.format method explicitly sets :json as the format on XHR requests - does rails only respect XHR requests at the moment?
Thanks!

What you are doing is correct, but sending the Axios/Fetch API requests from browser will show "CORS" error from the browser end(you can see it in your browser dev tools console). You can know more about it from MDN Docs
To resolve this, You'll need to send the Access-Control-Allow-Origin Header to the requests you receive on your web server. You can do it manually by adding this header in application_controller.rb or Simply use a gem like rack-cors. I'll show you how to do using rack-cors gem:
Add gem 'rack-cors' in your Gemfile
Run the command bundle i
In your config/application.rb file, add the following lines:
config.middleware.insert_before 0, Rack::Cors do
allow do
origins '*'
resource '*', headers: :any, methods: [:get, :post, :options]
end
end
Now restart your rails server
You can also see above instructions and more details here
Now make the API call again without forcing .json at the end of the URL and it should work.

Related

Angular with Rails API: "Failed to load resource: the server responded with a status of 401 (Unauthorized)"

So I am deploying an Angular 5 app with a Rails 5 back-end. I can get the data to flow properly between the two locally, but trying to connect to the deployed version of the API (which is on Heroku) I run into some authorization issue. The error is:
Failed to load https://my_api.herokuapp.com/data.json: No 'Access-Control-Allow-Origin' header is present on the requested resource.
Origin 'http://localhost:4200' is therefore not allowed access.
The response had HTTP status code 404.
Cross-Origin Read Blocking (CORB) blocked cross-origin response <URL> with MIME type application/json.
See <URL> for more details.
Is this something I need to change within the Rails API or in Angular? The deployed Rails API is essentially the same as the local version so I'm not sure where the disconnect is coming from.
There are only two refrences to the API in Angular. I connect to it the same way that I do to the local server:
Angular, app-module.ts
providers: [Angular2TokenService, AuthService, AuthGuard,
// {provide: 'api', useValue: 'http://localhost:3000/'}
{provide: 'api', useValue: 'https://my_ api.herokuapp.com/data.json'}
]
Perhaps it's my use of Angular2TokenService?
Angular, environment.ts:
export const environment = {
production: false,
token_auth_config: {
// apiBase: 'http://localhost:3000'
apiBase: 'https://my_api.herokuapp.com/data.json'
}};
Thanks! Let me know of any suggestions you might have or if you need clarification.
It's issue with CORS(cross-origin-resource-sharing). You can handle it by adding callback in your API like below:
def cors_set_access_control_headers
headers['Access-Control-Allow-Origin'] = ENV['SERVER_URL'] || '*'
end
where SERVER_URL is your front-end server URL
Else you can use gem 'rack-cors' as suggested in comments by #Kedarnag Mukanahallipatna

how to access response headers in React from Rails api-pagination gem

So I got this gem to work in limiting the amounts returned in the JSON response. However I'm not sure how to access the links in the headers that let you get to the next and previous pages.
Here is my controller:
def index
movies = Movie.all
paginate json: movies, per_page: 50
end
This is the part of the readme for the gem that says you can access next and prev through the headers:
$ curl --include 'https://localhost:3000/movies?page=5'
HTTP/1.1 200 OK
Link: <http://localhost:3000/movies?page=1>; rel="first",
<http://localhost:3000/movies?page=173>; rel="last",
<http://localhost:3000/movies?page=6>; rel="next",
<http://localhost:3000/movies?page=4>; rel="prev"
Total: 4321
Per-Page: 10
# ...
I can see them in Postman, but have yet to find a way to access them in my React front end. Thank you for any help you're able to offer.
This is the url for the gem: api-pagination gem
Are you accessing this data in React through a fetch request? Something like
fetch('url').then(res => {res.json()})
You should be able to access headers with fetch('url').then(res => res.headers). You can then manipulate them or save them to state or whatever you want to do with them. Fetch API docs here.
Actually it worked to me. if anyone come here by searching.
Add expose: ['header-key'] in cors configuration in your rails app.
Rails.application.config.middleware.insert_before 0, Rack::Cors do
allow do
origins '*'
resource '*',
headers: :any,
methods: [:get, :post, :put, :patch, :delete, :options, :head], expose: ['X-Pagination']
end
end
After adding expose, you'll be able to get the header key in your client-side code by using response.headers.get("X-Pagination").
Note:- I verified it by using fetch API call resonse.
What I did with in a similar situation was to add the following in my rails controller:
response.header['my_attribute_name'] = my_value
headers['Access-Control-Expose-Headers'] = 'my_attribute_name'
Then on the front-end I just used:
response.headers.get("my_attribute_name")
This solved my issue.
response.headers.get('Link') solved it!
This is a CORS restriction.
White list headers (by implementing Access-Control-Expose-Headers in the server) that you are using for pagination.
Only then you can access any header on a CORS request.
Please also note that, this restriction does not apply on requests from the same origin.

rails 4 API gives 401 unathorized response after successful log in using angular2-token package

I have a setup in which I have a rails 4 API having the gem devise_token_auth and hosted as a separate application so I have also rack-cors configured to handle cross origin requests. Using angular2-token on my front end Angular 2 applicaiton I have been able to successfully sign up and sign in as well as sign out users via my API.
The issue however, which I have encountered occurs only when the user is signed in and upon refreshing the browser I get this error in the rails API console as well as in the browser, checked in firefox as well as chrome.
Started GET "/api/v1/auth/validate_token" for 127.0.0.1 at 2017-02-06 17:42:49 +0500
Processing by DeviseTokenAuth::TokenValidationsController#validate_token as JSON
followed by
SELECT "users".* FROM "users" WHERE "users"."uid" = $1 LIMIT 1 [["uid", "abc#xyz.com"]]
Completed 401 Unauthorized in 76ms (Views: 0.2ms | ActiveRecord: 0.3ms)
My initial assumption during the configuration of this package in my Angular2 app was that it will implicitly include authentication headers in each request. However after repeatedly going through the gem's documentation I also added the headers myself when I initialize the token service in my app.component.ts file.
this._tokenService.init({
apiPath: API_PATH,
globalOptions: {
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
"access_token_name": localStorage.getItem('accessToken'),
"client_name": localStorage.getItem('client'),
"uid_name": localStorage.getItem('uid')
}
}
});
Even after that the response hasn't changed to the request and I was unable to receive these headers on the server end as well.
However after hours of inspection an idea finally came to me which was to inspect the headers m getting on the server and when I used ruby's request.header.inspect on my server end application I get the following output with the information required for validation of the token but it seems that the name of the keys of these header values are different form what the devise_token_auth expects to validate token (I went through the source of the devise_auth_token gem here.
"HTTP_ACCESS_TOKEN_NAME"=>"xxxxxxxxxxxxxxxxxx", "HTTP_EXPIRY"=>"xxxxxxxxxxxxxxxxxx", "HTTP_UID"=>"abc#xyz.com", "HTTP_CLIENT_NAME"=>"xxxxxxxxxxxxxxxxxx", "HTTP_TOKEN_TYPE"=>"Bearer"
What I believe is the user is not being set by the devise_token_auth gem based on the headers that are being passed.
After repeatedly going through the documentation of Angular2-token as well as devise_token_auth gem I am confused whether or not to manually add headers for authentication because I believe they are being passed already but with different keys.
I would just like to know if that is the case I am experiencing its been almost a full day and I cannot figure out a way to pin point the reason behind the 401 response.
Thanks a lot.
EDITED:
Moreover I am also getting nil when accessing current_user or any devise helper after successful sign in on server end.
Here are the rack-cors configuration for my api rails applicaiton as well.
application.rb
config.middleware.use Rack::Cors do
allow do
origins '*'
resource '/cors',
:headers => :any,
:methods => [:post],
:credentials => true,
:max_age => 0
resource '*',
:headers => :any,
:expose => ['access-token', 'expiry', 'token-type', 'uid', 'client'],
:methods => [:get, :post, :options, :delete, :put]
end
end
The headers I get upon inspecting are following:
HTTP_ACCESS_TOKEN
HTTP_CLIENT
HTTP_EXPIRY
HTTP_TOKEN_TYPE
HTTP_UID
These are the headers sent even if I don't mention any headers while configuring the angular2-token package.
I am confused why it lets me login in the first place and later throw an error with a 401 code and response of
{"success":false,"errors":["Invalid login credentials"]}
When I try and manually check token's validation using the following code
this._tokenService.validateToken().subscribe(
res => console.log(res),
error => console.log(error)
);
You should also pass Expiry and Token-type on requests for devise_token_auth to authenticate, something like this:
let headers = new Headers();
headers.append('Content-Type', 'application/json');
headers.append('Uid', this.uid);
headers.append('Client', this.client);
headers.append('Access-Token', this.access_token);
headers.append('Expiry', this.expiry);
headers.append('Token-Type', 'Bearer');
this.http.post('http://my-api.com/', JSON.stringify(resource), {headers: header}).subscribe((res)=>{
#Your Logic Here
});
This example is for generic HTTP requests, but you can apply that rule on your angular token plugin. ie.:
this._tokenService.init({
apiPath: API_PATH,
globalOptions: {
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
"access_token_name": localStorage.getItem('accessToken'),
"client_name": localStorage.getItem('client'),
"uid_name": localStorage.getItem('uid'),
"expiry_name": localStorage.getItem('expiry'),
"token-type_name': 'Bearer'
}
}
});
You have set custom headers name for devise_token_auth? First example works with default configuration, without _name in the end of the headers' names, you should try modifying if that is the case.
After spending a few days on this issue and going through multiple threads of related issues repeatedly posted on related topics I came across the following issue and I realized I have rails 4 and have used rails-api gem to generate my API.
After that I created a rails 5 API with --api option (without rails-api gem) and with devise_token_auth and rack-cors on my api end I was successful in sending authorized request using the angular2-token package. Along with that I was also able to send authorized http post requests with the authorization headers access-token, token-type, expiry, uid as mentioned in the devise_token_auth gem's documentation.
This might not be the exact solution or I may not have pinpointed the cause of the issue but this was what worked for me.

Swagger-ui only sending OPTIONS not POST http method despite working API

I am using Swagger-UI to browse my own API, built with grape and automatically documented with grape-swagger.
I've googled and tried every suggestion I can find, but I cannot get POST to work. Here's my headers:
header "Access-Control-Allow-Origin", "*"
header "Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, PATCH, DELETE"
header "Access-Control-Request-Method", "*"
header "Access-Control-Max-Age", "1728000"
header "Access-Control-Allow-Headers", "api_key, Content-Type"
I just threw in everything suggested. I've enabled all the HTTP methods in supportedSubmitMethods and I have tested the API using the POSTMAN Chrome extension and it works perfectly. Creates a user properly and returns the correct data.
However all I get with swagger post is the server reporting:
Started OPTIONS "/v1/users.json" for 127.0.0.1 at 2012-12-21 04:07:13 -0800
and swagger response looking like this:
Request URL
http://api.lvh.me:3000/v1/users.json
Response Body
Response Code
0
Response Headers
I have also tested the OPTIONS response with POSTMAN and it is below:
Allow →OPTIONS, GET, POST
Cache-Control →no-cache
Date →Fri, 21 Dec 2012 12:14:27 GMT
Server →Apache-Coyote/1.1
X-Request-Id →9215cba8da86824b97c6900fb6d97aec
X-Runtime →0.170000
X-UA-Compatible →IE=Edge
I had the same problem and just solved it, hope this helps somebody.
Swagger-UI accepts multiple parameters through POST only through a 'form' paramType, not 'body' paramType, referenced in this issue https://github.com/wordnik/swagger-ui/issues/72.
I used the branch :git => 'git://github.com/Digication/grape-swagger.git' changing 'post' request paramType to 'form'. Generated xml output for swagger_doc (probably at path/swagger_doc/api or similar) should look something like this:
<api>
<path>/api/v2/...</path>
<operations type="array">
...
<httpMethod>POST</httpMethod>
<parameters type="array">
<parameter>
<paramType>form</paramType>
...More
Not
<paramType>body</paramType>
...More
I used the grape-swagger-rails gem to automatically install swagger-ui on localhost (files can also be downloaded from the swagger-ui site), and everything works!!
Had the same problem. Fixed by adding CORS
add into Gemfile:
gem 'rack-cors', :require => 'rack/cors'
add into application.rb
config.middleware.use Rack::Cors do
allow do
origins '*'
# location of your API
resource '/*', :headers => :any, :methods => [:get, :post, :options, :put]
end
end
be sure that you've changed location of your API here.
Nice to hear you are using grape-swagger: I think it is awesome :)
I am not entirely sure you are having the same problem, but when testing locally from the browser it will try to check if the origin is the same as requested, so to make sure I do not get that error, I created a small middleware that will tell the browser we allow all origin.
I am using a rails process (created with the awesome rails-api gem), so I create a new file in lib/middleware/access_control_allow_all_origin.rb with the following content:
module Middleware
class AccessControlAllowAllOrigin
def initialize(app)
#app = app
end
def call(env)
status, headers, body = #app.call(env)
allow_all_origin!(headers)
[status, headers, body]
end
private
def allow_all_origin!(headers)
headers['Access-Control-Allow-Origin'] = '*'
headers['Access-Control-Request-Method'] = '*'
end
end
end
and at the bottom of my application.rb I just add the middleware as follows:
require 'middleware/access_control_allow_all_origin'
config.middleware.insert_after Rack::ETag, Middleware::AccessControlAllowAllOrigin
Hope this helps.
I do not know about the solution for ruby-on-rails as I am using Swagger with play framework 2.0.2.
I provided a domain name to it and changed the basePath to domain name in application.conf file as swagger.api.basepath="domain-name" and it worked.
You can change the basePath in api-docs to domain-name. I read about the api-docs on
api-docs.
And does your web server hijack headers? If you are using NGinx for example, your "OPTIONS" request might not send the appropriate values as a response, in some cases.
What is your OPTIONS request response? Can you dump it out here? I'll tell you if it can be that.

Ruby Proxy Authentication GET/POST with OpenURI or net/http

I'm using ruby 1.9.3 and trying to use open-uri to get a url and try posting using Net:HTTP
Im trying to use proxy authentication for both:
Trying to do a POST request with net/http:
require 'net/http'
require 'open-uri'
http = Net::HTTP.new("google.com", 80)
headers = { 'User-Agent' => 'Ruby 193'}
resp, data = http.post("/", "name1=value1&name2=value2", headers)
puts data
And for open-uri which I can't get to do POST I use:
data = open("http://google.com/","User-Agent"=> "Ruby 193").read
How would I modify these to use a proxy with HTTP Authentication
I've tried (for open-uri)
data = open("http://google.com/","User-Agent"=> "Ruby 193", :proxy_http_basic_authentication => ["http://proxy.com:8000/", "proxy-user", "proxy-password"]).read
However all I will get is a OpenURI::HTTPError: 407 Proxy Authentication Required. I've verified all and it works in the browser with the same authentication and proxy details but I can't get ruby to do it.
How would I modify the code above to add http authentication properly? Has anyone gone through this atrocity?
Try:
require "open-uri"
proxy_uri = URI.parse("http://proxy.com:8000")
data = open("http://www.whatismyipaddress.com/", :proxy_http_basic_authentication => [proxy_uri, "username", "password"]).read
puts data
As for Net::HTTP, I recently implemented support for proxies with http authentication into a Net::HTTP wrapper library called http. If you look at my last pull-request, you'll see the basic implementation.
EDIT: Hopefully this will get you moving in the right direction.
Net::HTTP::Proxy(proxy_uri.host, proxy_uri.port,"username","password").start('whatismyipaddress.com') do |http|
puts http.get('/').body
end
EDIT 11/24/2020: Net::HTTP::Proxy is now considered obsolete. You can now configure proxies when creating a new instance of Net::HTTP. See the documentation for Net::HTTP.new for more details.

Resources