Rails 6 CORS issue when accessing azure AD - ruby-on-rails

I am trying to implement devise/oauth to azure AD with steps from this article.
https://medium.com/committed-engineers/setup-azure-ad-oauth-2-0-with-ruby-on-rails-and-devise-39848e3ed532
It looks that gem is working, I get link to login.microsoft.com with params, which is sent by post method, but I get CORS error
Access to fetch at
'https://login.microsoftonline.com/xxxxxx/oauth2/v2.0/authorize?client_id=zzzz&prompt&redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fusers%2Fauth%2Fazure_activedirectory_v2%2Fcallback&response_type=code&scope=openid+profile+email&state=xxxxx'
(redirected from
'http://localhost:3000/users/auth/azure_activedirectory_v2') from
origin 'http://localhost:3000' has been blocked by CORS policy:
Response to preflight request doesn't pass access control check: No
'Access-Control-Allow-Origin' header is present on the requested
resource. If an opaque response serves your needs, set the request's
mode to 'no-cors' to fetch the resource with CORS disabled.
When I check network in console, I see that headers looks fine
> Request URL: http://localhost:3000/users/auth/azure_activedirectory_v2
> Request Method: POST Status Code: 302 Found Remote Address: [::1]:3000
> Referrer Policy: strict-origin-when-cross-origin
> Access-Control-Allow-Credentials: true Access-Control-Allow-Methods:
> GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS
> Access-Control-Allow-Origin: http://localhost:3000
> Access-Control-Expose-Headers Access-Control-Max-Age: 7200
> Cache-Control: no-cache Content-Length: 361 Location:
> https://login.microsoftonline.com/xxxx
I use rack-cors gem with this config in cors.rb
Rails.application.config.middleware.insert_before 0, Rack::Cors do
allow do
origins 'http://localhost:3000'
resource '*',
headers: :any,
methods: :any,
credentials: true
end
end
Rails.application.config.hosts << ".microsoft.com"
What am I missing? When I manually open location of that post request I see in console, I get login screen for microsoft account what is expected
edit: When I enable debug in cors.rb, I see that just to my post request there is no header
Incoming Headers:
Origin: http://localhost:3000
Path-Info: /mini-profiler-resources/results
Access-Control-Request-Method:
Access-Control-Request-Headers:
{"Access-Control-Allow-Origin"=>"http://localhost:3000", "Access-Control-Allow-Methods"=>"GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS", "Access-Control-Expose-Headers"=>"", "Access-Control-Max-Age"=>"7200", "Access-Control-Allow-Credentials"=>"true"}
Incoming Headers:
Origin: http://localhost:3000
Path-Info: /mini-profiler-resources/results
Access-Control-Request-Method:
Access-Control-Request-Headers:
{"Access-Control-Allow-Origin"=>"http://localhost:3000", "Access-Control-Allow-Methods"=>"GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS", "Access-Control-Expose-Headers"=>"", "Access-Control-Max-Age"=>"7200", "Access-Control-Allow-Credentials"=>"true"}
Incoming Headers:
Origin: http://localhost:3000
Path-Info: /mini-profiler-resources/results
Access-Control-Request-Method:
Access-Control-Request-Headers:
{"Access-Control-Allow-Origin"=>"http://localhost:3000", "Access-Control-Allow-Methods"=>"GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS", "Access-Control-Expose-Headers"=>"", "Access-Control-Max-Age"=>"7200", "Access-Control-Allow-Credentials"=>"true"}
Incoming Headers:
Origin: http://localhost:3000
Path-Info: /users/auth/azure_activedirectory_v2
Access-Control-Request-Method:
Access-Control-Request-Headers:

ok for everybody who will spend lot of time trying to find a problem
https://accidentaltechnologist.com/ruby-on-rails/hotwire-fix-for-cors-error-when-using-omniauth/
Actually, only thing needs to be done is change link_to to button_to helper and disable turbo for it, so add data-turbo false
this is how it should look and all works well, I can authenticate with AD
<%= button_to "Log in with Azure AD", user_azure_activedirectory_v2_omniauth_authorize_path, method: :post, data: {turbo: "false"}, class: "btn btn-primary" %>

The short solution is to add data: {turbo: "false"} to the link to trigger the OAuth request.

Related

Sending cookie in XHR response but it's not getting stored?

I'm trying to pass an HttpOnly cookie with a response from an API I'm writing. The purpose is for the cookie act like a refresh token for the purpose of silent refresh for a SPA in React.
In my controller method, I've got the following code:
response.set_cookie(
:foo,
{
value: 'testing',
expires: 7.days.from_now,
path: '/api/v1/auth',
secure: true,
httponly: true
}
)
I'm making a post request to this action with a fetch command like so:
fetch("http://localhost:3001/api/v1/auth", {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
email: 'aaron#example.com',
password: '123456',
})
})
Not sure if that matters but I'm wondering if passing cookies in a XHR response doesn't work? However, this seems to be working as you can see in my response I'm getting this:
Set-Cookie: foo=testing; path=/api/v1/auth; expires=Sun, 26 Jan 2020 05:15:30 GMT; secure; HttpOnly
Also in the Network tab under Cookies I'm getting this:
However, I'm NOT getting the cookie set under Application -> Cookies:
To clarify, the React app is sitting on localhost:3000 and the rails backend is listening on localhost:3001.
Any ideas?
Ok, so it looks like I needed to configure my CORS (in Rails this is your Rack::CORS middleware.
I setup my config/initializers/cors.rb file like so:
Rails.application.config.middleware.insert_before 0, Rack::Cors do
allow do
origins 'http://localhost:3000'
resource '*',
headers: :any,
methods: [:get, :post, :put, :patch, :delete, :options, :head],
credentials: true
end
end
and my fetch command should look something like this with credentials: 'include' as a parameter:
return fetch(`${endPoint}`, {
method: 'POST',
credentials: 'include',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
email,
password,
password_confirmation: passwordConfirmation
})
})
Adding credentials: true allows cookies to be set by the browser. Apparently, even if you send them, you need Access-Control-Allow-Credentials: true in the headers for the browser to do anything with them.
EDIT: Recreating this application for learning experience I came across this issue again even after including the credentials option. I wasn't seeing the HttpOnly cookie being stored in the browser. Turns out however, that it WAS and does get sent. You can probably test for this in a controller action. Keep this in mind if this solution doesn't 'seem' to work for you!

No 'Access-Control-Allow-Origin' header is present on the requested resource?

My bootstrap glyphicons show on other browsers, but I get this error on google chrome:
Font from origin 'http://d37p52igaahgm9.cloudfront.net' has been
blocked from loading by Cross-Origin Resource Sharing policy: No
'Access-Control-Allow-Origin' header is present on the requested
resource. Origin 'http://www.anthonygalli.com' is therefore not
allowed access.
The error persists despite trying:
application_controller.rb
before_action :set_cors
def set_cors
headers['Access-Control-Allow-Origin'] = '*'
headers['Access-Control-Request-Method'] = '*'
end
application.rb
config.middleware.insert_before 0, "Rack::Cors" do
allow do
origins '*'
resource '*', :headers => :any, :methods => [:get, :post, :options]
end
end
config.action_dispatch.default_headers = {
'Access-Control-Allow-Origin' => '*',
'Access-Control-Request-Method' => '*'
}
CORS Configuration Editor
<?xml version="1.0" encoding="UTF-8"?>
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
<CORSRule>
<AllowedOrigin>https://www.anthonygalli.com</AllowedOrigin>
<AllowedMethod>GET</AllowedMethod>
<MaxAgeSeconds>3000</MaxAgeSeconds>
<AllowedHeader>Content-*</AllowedHeader>
<AllowedHeader>Host</AllowedHeader>
</CORSRule>
<CORSRule>
<AllowedOrigin>https://anthonygalli.com</AllowedOrigin>
<AllowedMethod>GET</AllowedMethod>
<MaxAgeSeconds>3000</MaxAgeSeconds>
<AllowedHeader>Content-*</AllowedHeader>
<AllowedHeader>Host</AllowedHeader>
</CORSRule>
</CORSConfiguration>
REFERENCES
Amazon S3 CORS (Cross-Origin Resource Sharing) and Firefox cross-domain font loading
Font from origin has been blocked from loading by Cross-Origin Resource Sharing policy
I had everything properly configured:
# config/initializers/cors.rb
Rails.application.config.middleware.insert_before 0, Rack::Cors do
allow do
origins "*"
resource "*",
headers: :any,
methods: [:get, :post, :put, :patch, :delete, :options, :head]
end
end
And still I was having the error:
Access to XMLHttpRequest at 'https://playcocola.com/api/' from origin 'https://client.playcocola.com' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
It was happening randomly, only in production and only in some requests, not all.
The problem was related with the size of the uploaded file and my nginx configuration in production. The solution was here: CORS error upload file ~4mb
# nginx.conf
http {
[...]
client_max_body_size 50M;
}
You don't need to (shouldn't be) generating the headers in every response.
In your case, I would wager the asset request from your browser is being "preflighted" with an OPTIONS request, but the CDN passes on the request without Access-Control request headers. The CDN thus (correctly) receives no CORS response headers from your Rails app, so the browser doesn't even attempt the GET request, and fails with the Cross-Origin error.
"preflighted" requests first send an HTTP request by the OPTIONS method to the resource on the other domain, in order to determine whether the actual request is safe to send
https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS#Preflighted_requests
Your CDN needs be set up to forward the correct request headers to your app server such that it knows to generate the CORS headers. Then, the CDN will pass these CORS response headers along to the browser.
When you want OPTIONS responses to be cached, configure CloudFront to forward the following headers: Origin, Access-Control-Request-Headers, and Access-Control-Request-Method.
http://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/header-caching.html#header-caching-web-cors
If you make the change to your CDN for those headers and then invalidate your assets, your rack-cors configuration by itself should work just fine.
# config/initializers/cors.rb
# #note: must be run after initializers/_assets.rb
Rails.application.config.middleware.insert_before 0, Rack::Cors do
allow do
origins '*'
# All asset requests should be to rails prefixed assets paths
# serverd from the asset pipeline (e.g.: "/assets/*" by default)
resource "#{Rails.application.config.assets.prefix}/*",
# Allow any request headers to be sent in the asset request
# https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS#Access-Control-Allow-Headers
headers: :any,
# All asset fetches should be via GET
# Support OPTIONS for pre-flight requests
# https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS#Preflighted_requests
methods: [:get, :options]
end
end
Try adding method and headers in application controller. It worked for me.
def cors_set_access_control_headers
headers['Access-Control-Allow-Origin'] = '*'
headers['Access-Control-Allow-Methods'] = 'POST, PUT, DELETE, GET, PATCH, OPTIONS'
headers['Access-Control-Request-Method'] = '*'
headers['Access-Control-Allow-Headers'] = 'Origin, X-Requested-With, Content-Type, Accept, Authorization'
end

Completed 401 unauthorized The request was redirected to which is disallowed for cross-origin requests that require preflight

This question may sound like a duplicate, but I have tried all steps in the other similar questions on stackoverflow but none of them worked. I am trying to connect to a Rails API using ionic.
My setup is
Rails 4, Devise for authentication, simple_token_authentication gem for token based API requests and ionic for mobile app.
I'm able to login from ionic by passing a login request with a correct username and password to the devise session controller api and get a token after successful login through the devise api.
After that, when I try to access any controller's index with a get query using the access token:
http://localhost:3002/api/categories?token=1098ccee839952491a4 or http://localhost:3002/api/employers?token=1098ccee839952491a4
The response is a sign_in page of devise. In the rails log, it shows
Started GET "/api/categories?token=1098ccee839952491a43" for ::1 at 2016-06-17 17:45:55 +0530
Processing by Api::CategoriesController#index as HTML
Parameters: {"token"=>"1098ccee839952491a43"}
User Load (0.7ms) SELECT `users`.* FROM `users` WHERE `users`.`authentication_token` = '1098ccee839952491a43'
Completed 401 Unauthorized in 3ms (ActiveRecord: 0.7ms)
Started GET "/users/sign_in" for ::1 at 2016-06-17 17:45:55 +0530
Processing by Devise::SessionsController#new as HTML
Rendered devise/shared/_links.html.erb (6.5ms)
Rendered devise/sessions/new.html.erb within layouts/login (78.3ms)
Completed 200 OK in 3850ms (Views: 3847.1ms | ActiveRecord: 0.0ms)
I know for sure the token is correct as I have cross checked the token in the database. Also, I have included rack-cors gem and its configured for allowing any origin for cross domain request.
In my application.rb the cors entry looks like this
class Application < Rails::Application
config.active_record.raise_in_transactional_callbacks = true
config.middleware.insert_before 0, "Rack::Cors" do
allow do
origins '*'
resource '*', :headers => :any, :methods => [:get, :post, :options]
end
end
end
The GET request in controllers.js in ionic looks like this :-
$http({
method: 'GET',
headers:
{
'X-XSRF-TOKEN': window.localStorage.getItem("token") ,
},
url: config.apiUrl+"/categories?token="+window.localStorage.getItem("token") ,
}).then(function successCallback(response) {
console.log(response);
}, function errorCallback(response) {
$ionicPopup.alert({
title: 'Alert',
template: response.data.message,
});
});
After the request is initiated, Chrome developer console shows the following message
XMLHttpRequest cannot load
http://localhost:3002/api/categories?token=1098ccee839952491a43. The
request was redirected to 'http://localhost:3002/users/sign_in', which
is disallowed for cross-origin requests that require preflight.
The response headers I get from the server is
General
Request URL:http://localhost:3002/api/categories?token=1098ccee839952491a43
Request Method:GET
Status Code:302 Found
Remote Address:[::1]:3002
Response Headers
view source
Access-Control-Allow-Credentials:true
Access-Control-Allow-Methods:GET, POST, OPTIONS
Access-Control-Allow-Origin:http://localhost:8100
Access-Control-Expose-Headers:
Access-Control-Max-Age:1728000
Cache-Control:no-cache
Connection:Keep-Alive
Content-Length:101
Content-Type:text/html; charset=utf-8
Date:Fri, 17 Jun 2016 12:26:13 GMT
Location:http://localhost:3002/users/sign_in
Server:WEBrick/1.3.1 (Ruby/2.2.1/2015-02-26)
Set-Cookie:_backend_session=c05LRTgzdXNVdGNFeHcwaDNtRDkxZ1RBbXFNaDFNbE1zMTV6OGhFZG1VODdON2k4ODN0ZTg3YXo5OU5hOTZkcUNmR3VTaGVRQzBOUElCNnMrODhnemVCbUhQbUs2azNHdmVnbFpoL2JiRDA5S1I4NCtsVWNhOEpqeDdoYWxXR3hHNDg5RzNRcUlpcDZ4QWswY2NmUlpYbUkvTnpaVEViNXh6ZExCZkNyTXA0allMcW1RVGg1MjRLVnpEbzlSSVk4dGRUSTNsTUZFNEFOcVJxdm1hWXNaSFEzdnlSN01DZ2FTdEJYUTdUUGxucU44cFBMbDZ6RXlhVUx2ZHArWlNCVWgyTWs2emRrSXNGbTQ3ZVVQL0NCR0E9PS0tbmFzajFXZnZnd25oUkRFRjhxSy81QT09--281b2bd6413653ca006abc80f13329d6bf3ad0f5; path=/; HttpOnly
Vary:Origin
X-Content-Type-Options:nosniff
X-Frame-Options:SAMEORIGIN
X-Request-Id:3097ec28-6d55-4c70-90c9-de221b4321e3
X-Runtime:0.021444
X-Xss-Protection:1; mode=block
Request Headers
view source
Accept:application/json, text/plain, */*
Accept-Encoding:gzip, deflate, sdch
Accept-Language:en-US,en;q=0.8
Connection:keep-alive
Host:localhost:3002
Origin:http://localhost:8100
Referer:http://localhost:8100/
User-Agent:Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.84 Safari/537.36
X-XSRF-TOKEN:1098ccee839952491a43
I think the issue related with Access-Control-Allow-Credentials:true header, try this:
$http({
method: 'GET',
withCredentials: true,
headers:
{
'X-XSRF-TOKEN': window.localStorage.getItem("token") ,
},
url: config.apiUrl+"/categories?token="+window.localStorage.getItem("token") ,
}).then(function successCallback(response) {
console.log(response);
}, function errorCallback(response) {
$ionicPopup.alert({
title: 'Alert',
template: response.data.message,
});
});
it will send cookies to api server as well
try this
app.config(function($sceDelegateProvider) {
$sceDelegateProvider.resourceUrlWhitelist([
// Allow same origin resource loads.
'self',
// Allow loading from our assets domain. Notice the difference between * and **.
'http://localhost:3002/**'
]);
});
Browser automatically bloc resource from a stranger url you need to allow domain.

Allow CORS in Ruby on Rails

In my config/application.rb file, I have this code,
config.action_dispatch.default_headers = {
'Access-Control-Allow-Origin' => '*',
'Access-Control-Request-Method' => 'GET, PATCH, PUT, POST, OPTIONS, DELETE'
}
But that does not allow me to send a post request to a route on my sever
Safari gives this error:
http://localhost:3000/studentsFailed to load resource: the server responded with a status of 404 (Not Found)
http://localhost:3000/studentsFailed to load resource: Origin http://localhost:4200 is not allowed by Access-Control-Allow-Origin.
localhost:1XMLHttpRequest cannot load http://localhost:3000/students. Origin http://localhost:4200 is not allowed by Access-Control-Allow-Origi
And in my Rails server console:
Started OPTIONS "/students" for ::1 at 2015-03-28 21:00:45 -0500
ActionController::RoutingError (No route matches [OPTIONS] "/students"):
I spent some time working on this and I can tell you the most reliable solution is to use rack-cors. see: https://github.com/cyu/rack-cors
First add the gem:
gem 'rack-cors', '~> 0.3.1'
then in application.rb add
config.middleware.insert_before ActionDispatch::Static, Rack::Cors do
allow do
origins '*'
resource '*', :headers => :any, :methods => [:get, :post, :options]
end
end
If your production app does not serve static assets (such as if you use a serve like nginx or apache), consider replacing ActionDispatch::Static in the above example with 0. See https://github.com/cyu/rack-cors#common-gotchas for more information about the argument.
I was able to figure this out with a bit of help from #Akiomi's answer:
In my routes.rb, I added the following code to the top of the file:
match '(:anything)' => 'application#nothing', via: [:options]
Next, in my application controller, I added:
def nothing
render text: '', content_type: 'text/plain'
end
Along with the headers in config/application.rb:
config.action_dispatch.default_headers = {
'Access-Control-Allow-Origin' => '*',
'Access-Control-Request-Method' => 'GET, PATCH, PUT, POST, OPTIONS, DELETE',
'Access-Control-Allow-Headers:' => 'Origin, X-Requested-With, Content-Type, Accept'
}
Yes, notice the 'Access-Control-Allow-Headers:' => 'Origin, X-Requested-With, Content-Type, Accept' that was not included in my original question, this is one of the big problems.
Add the following code:
In config/routes.rb:
match 'students' => 'students#option', via: [:options]
In controllers/student_controller.rb:
def option
render text: '', content_type: 'text/plain'
end
Or you can use rack-cors.
In some cases a browser will do a preflight request: rather than actually doing the request it first does an OPTIONS request to the same url, so that it can find out what the values of the various CORS headers are (More on preflighting here). If this request is successful and the headers have the correct values, it then does the actual request.
You haven't added a route for these options requests, so they're going through to the rails 404 page which doesn't include the CORS headers.
The OPTIONS response just needs to set the same CORS headers as you would normally set during a request. It shouldn't do anything else. For example
match 'students' => 'students#cors_preflight', via: [:options]
def cors_preflight
render nothing: true
end
Note that there are other CORS headers you may need to set such as Access-Control-Allow-Credentials, Access-Control-Allow-Headers
When you've got this working you may wish to consider tightening this up a little - you are potentially opening your app to cross site scripting attacks.
Rails 5
config.middleware.insert_before 0, Rack::Cors do
allow do
origins '*'
resource '*', headers: :any, methods: [:get, :post, :options]
end
end

Ember app that consumes json getting error "XMLHttpRequest cannot load"

I have a rails app on heroku that is serving up data via an API. This data looks like...
[{"created_at":"2014-02-20T17:22:02Z","id":1,"name":"Joe Rogan","twitter":"#joerogan","updated_at":"2014-02-20T17:22:02Z"},{"created_at":"2014-02-20T17:22:11Z","id":2,"name":"Kristen Schaa","twitter":"#kchalithis","updated_at":"2014-02-20T17:22:11Z"},{"created_at":"2014-02-20T17:29:10Z","id":3,"name":"Casey Grim","twitter":"#aCoupleofN3rds","updated_at":"2014-02-20T17:29:16Z"}]
I pulled down the Ember Start Kit and have modified it to pull data from my API with code like this...
js/app.js
App.Person.adapter = Ember.RESTAdapter.create();
App.Person.url = "http://myapp.herokuapp.com/api/v1/shots?api_key=12d2d06fb2f6a786ac75b32625cf83a1";
App.Person.collectionKey = "people";
index.html
<script type="text/x-handlebars" id="index">
<ul>
{{#each item in model}}
<li>{{item.name}}</li>
{{/each}}
</ul>
</script>
I thought I could start chrome like this...
google-chrome --disable-web-security
And get it working just for testing, but that does not work.
Also, Sounds like I need to use jsonp rather than json? But not sure how to implement in ember (or really anywhere else). Any help appreciated. Thanx!
Update
Added :cors_set_access_control_headers before filter in my rails app. looks like...
module Api
module V1
class ShotsController < ApplicationController
before_filter :cors_set_access_control_headers
...
def cors_set_access_control_headers
headers['Access-Control-Allow-Origin'] = '*'
headers['Access-Control-Allow-Methods'] = 'POST, PUT, DELETE, GET, OPTIONS'
headers['Access-Control-Request-Method'] = '*'
headers['Access-Control-Allow-Headers'] = 'Origin, X-Requested-With, Content-Type, Accept, Authorization'
end
...but still getting error
XMLHttpRequest cannot /api/v1/shots?api_key=d26da3938adc5f3c8604256194c18501.json. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'null' is therefore not allowed access.
...if I issue a curl to look at headers I'm seeing Access Controll set to *...
me#me-E6530:~$ curl -v http://myapp.herokuapp.com/api/v1/shots?api_key=12d2d06fb2f6a786ac75b32625cf83a1
* About to connect() to myapp.herokuapp.com port 80 (#0)
* Trying 54.243.169.60... connected
> GET /api/v1/shots?api_key=12d2d06fb2f6a786ac75b32625cf83a1 HTTP/1.1
> User-Agent: curl/7.22.0 (i686-pc-linux-gnu) libcurl/7.22.0 OpenSSL/1.0.1 zlib/1.2.3.4 libidn/1.23 librtmp/2.3
> Host: myapp.herokuapp.com
> Accept: */*
>
< HTTP/1.1 200 OK
< Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept, Authorization
< Access-Control-Allow-Methods: POST, PUT, DELETE, GET, OPTIONS
< Access-Control-Allow-Origin: *
< Access-Control-Request-Method: *
Ember expects response in the following format:
{
people: [
{
id: 1,
name: "John Doe"
},
{
id: 2,
name: "Jane Doe"
}
]
}
Also you may need to enable CORS if they are on different domains.

Resources