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.
Related
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
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.
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.
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.
I am testing out a webrtc demo for rails, but am having terrible connectivity issues. I have tried multiple things online but cannot crack this, it might be a simple mistake or a fundamental misunderstanding, but what I have done is this:
webrtc-rails using NodeJS and socket.io
I am using localhost:3000 for the home url of the page but am trying to connect from client to server via localhost:2013. I have two error messages at the moment:
No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:3000' is therefore not allowed access.
and
info - unhandled socket.io url (when running node server.js)
My set up is this:
server.js
var http = require('http');
var app = http.createServer(function (req, res) {
}).listen(2013);
client.js
var socket = io.connect('localhost:2013');
socket.on("created", function (){
console.log("On Created");
isInitiator = true;
console.log('isInitiator', isInitiator);
})
application.rb
config.middleware.insert_before 0, Rack::Cors do
allow do
origins '*'
resource '*', :headers => :any, :methods => [:get, :post, :options]
end
end
config.ru
require 'rack/cors'
use Rack::Cors do
allow do
origins '*'
resource '*',
:headers => :any,
:methods => [:get, :post, :delete, :put, :options, :patch]
end
end
I have updated all my gems and socket.io. I keep wondering whether there is a problem finding socket.io as if I navigate to page localhost:3000/socket.io/socket.io.js, there is nothing there as I would expect... I have also tried adding the CORS headers into the application rb rile without the middleware/rack gem, but it makes no difference. Any help much appreciated.
First, you would want to append "http" -- or whatever the protocol is -- to your urls that your are trying to connect to.
Second, instead of referencing localhost reference 127.0.0.1. Still reference the port number as you would normally.
var socket = io.connect('localhost:2013');
Would become:
var socket = io.connect('http://127.0.0.1:2013');
Edit: This would only work if your servers are assigned different IP addresses for some reason. That being said, you would still want to add the protocol to your urls anyway.