My environment is a Rails 7 API + React frontend.
The issue is that we need to call the API from the frontend only and prevent others urls to make any requests. I tried to configure this with the gem rack-cors and its file config/initializers/cors.rb as this (allowing only localhost:3001) :
Rails.application.config.middleware.insert_before 0, Rack::Cors do
allow do
origins 'http://localhost:3001'
resource '*',
headers: :any,
methods: %i[get post put patch delete options head],
expose: %w[Authorization Uid]
end
end
The problem is that a POST request made with POSTMAN is not blocked.
What did I miss ?
Thanks.
Julien
Related
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.
This question already has answers here:
Will CORS policy prevent resource access from non-browser requests?
(4 answers)
Why isn't my CORS configuration causing the server to filter incoming requests? How can I make the server only accept requests from a specific origin?
(1 answer)
Closed 3 years ago.
I have a Rails API and I have Rack::Cors setup in my application.rb to prevent requests from any origin other than https://my-website.com as:
config.middleware.insert_before 0, Rack::Cors do
allow do
origins 'https://my-website.com'
resource '*',
headers: %w[Authorization],
methods: %i[get post put delete options head],
expose: %w[Authorization]
end
end
Additionally I allow websocket connections as:
config.action_cable.url = %r{/wss:\/\/*/}
config.action_cable.allowed_request_origins = 'https://my-website.com'
And lastly I have a status checker for my Application Load Balancer and for that I allow http requests to /status as:
config.force_ssl = true
config.ssl_options = {
hsts: { subdomains: true },
redirect: { exclude: ->(request) { request.path =~ /status/ } }
}
So as you can see every path except /status should be https only but I am still getting errors such as below in my production error tracker:
#590 ActionController::RoutingError: No route matches [OPTIONS] "/"
In the details I get the source as:
How is it possible for someone to manage to reach my actual route when the origin is not added in my CORS configuration?
Rack::Cors only responds to OPTIONS request if request has Access-Control-Request-Method header, if someone issues that request without it - request will hit your regular routing.
Also note that CORS is only a method for browsers to allow cross-origin requests, anyone can use some non-browser script or tool to forge any requests they want.
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.
There are two servers:
one on domain A
another on different domain B.
Let's call them A-server and B-server respectively.
A-server is a main server with its own authentication and also frontend part.
B-server is a service with its own authentication.
How could I authenticate user on server B from server A within js?
I think about token-based authentication and add it to server B.
Now it could response to /sign.json path and well authenticate with token.
But token is placed in http-headers.
So, everything should be normal, right?
But these headers are hidden for js, because servers ( main A with js and B with token-based authentication ) have different domains.
Could somebody point to appropriate implementation of such kind of authentication?
Also, how it should be done well?
( For now it is too difficult to put authentication in one place and use it for all services/servers. )
It is a general question about http and tokens, but I have specific implementation:
B - Rails 4.2
gem 'devise_auth_token'
gem 'rack-cors'
//setup for rack-cors:
//in middlewares.rb
config.middleware.insert_before 0, "Rack::Cors" do
allow do
origins ENV['CONFIG_ACTION_DISPATCH_DEFAULT_HEADERS_CONTROL_ALLOW_ORIGIN'] || '*'
resource '*', headers: :any,
methods: [:get, :post, :options, :put, :patch, :delete],
expose: ['access-token', 'expiry', 'token-type', 'uid', 'client']
end
end
As you see from setup I add Expose-Headers from Token-based authentication.
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.