Apollo and GraphQL CORS - ruby-on-rails

Running into a frustrating issue with Apollo getting content from a Rails backend. The issue seems to be resolving around the use of CORS in my Apollo project.
Tech
apollo-client: 1.9.3
graphql: 0.11.7
react: 15.6.1
react-apollo: 1.4.16
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
rails is running on port 3001 rails s -p 3001
With this backend you can make curl requests and everything works as expected
Working Curl
curl -X POST -H "Content-Type: application/json" -d '{"query": "{users{first_name}}"}' http://localhost:3001/graphql
This returns back expected data
So this is all pointing to an issue with Apollo and the frontend of the application.
index.jsx
import React from 'react';
import ReactDOM from 'react-dom';
import ApolloClient from 'apollo-client';
import { ApolloProvider, createNetworkInterface } from 'react-apollo';
import App from './containers/App.jsx';
const client = new ApolloClient({
networkInterface: createNetworkInterface({
uri: 'http://localhost:3001/graphql', <<<<< There is a different endpoint then the standard 'graphql' which is why this is declared
})
});
ReactDOM.render(
<ApolloProvider client={client}>
<App />
</ApolloProvider>,
document.getElementById('root')
);
App.jsx
import React, { Component } from 'react';
import gql from 'graphql-tag';
import { graphql } from 'react-apollo';
class App extends Component {
render() {
console.log(this.props);
return (
<div>Application</div>
);
}
}
const query = gql`
{
users {
first_name
}
}
`;
export default graphql(query)(App);
This returns the error
Failed to load http://localhost:3001/graphql: Response to preflight
request doesn't pass access control check: No
'Access-Control-Allow-Origin' header is present on the requested
resource. Origin 'http://localhost:8080' is therefore not allowed
access. If an opaque response serves your needs, set the request's
mode to 'no-cors' to fetch the resource with CORS disabled.
app.jsx (change mode)
const client = new ApolloClient({
networkInterface: createNetworkInterface({
uri: 'http://localhost:3001/graphql',
opts: {
mode: 'no-cors'
}
})
});
This returns the error
Unhandled (in react-apollo) Error: Network error: Network request
failed with status 0 - ""`
Looking at the request:
GENERAL
Request URL:http://localhost:3001/graphql
Request Method:POST
Status Code:200 OK
Remote Address:[::1]:3001
Referrer Policy:no-referrer-when-downgrade
RESPONSE HEADERS
Cache-Control:max-age=0, private, must-revalidate
Content-Type:application/json; charset=utf-8
Transfer-Encoding:chunked
Vary:Origin
REQUEST HEADERS
Accept:*/*
Connection:keep-alive
Content-Length:87
Content-Type:text/plain;charset=UTF-8 <<< I'm wondering if this needs to be application/json?
Host:localhost:3001
Origin:http://localhost:8080
Referer:http://localhost:8080/
User-Agent:Chrome/61
REQUEST PAYLOAD
{query: "{↵ users {↵ first_name↵ __typename↵ }↵}↵", operationName: null}
operationName
:
null
query
:
"{↵ users {↵ first_name↵ __typename↵ }↵}↵"
So what I did to get some sort of response was to install the Chrome Extension Allow-Control-Allow-Origin: *
If mode: 'no-cors' is removed and this extension is active, data can be retrieved.
In looking through the Apollo docs I'm unable to find much on this topic. I tried implementing the Apollo Auth Header but this simply produced the same errors as above.
What in my Apollo code could be causing these errors? And what steps are there to fix the problem?
Searching over GitHub issues and other Google searches are either for much-older versions of Apollo in which issues "have been addressed" or do not work when implemented.
Edit
Adding Ruby on Rails tag just in case there is more configuration needed in Rails. Upon researching the Apollo Client issues found Network error: Network request failed with status 0 - "" This issued was resolved by the OP because of an issue on the backend.

Issue was in the cors.rb file
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
The issue was origins '*' had backticks instead of quotes
Was also able to remove the opts object in the index.jsx and everything is working as expected

Related

Random Prelight / OPTIONS requests hanging

Around November 17th we noticed in our production application that some of our IOS users we're experiencing HTTP requests that were hanging for sometimes 10-20 minutes before they would fail.
Example request:
curl 'https://api.example.com/api/' -H 'Accept: application/json, text/plain, */*' -H 'X-Custom-Header: accesstoken' -H 'timezone: America/Toronto' -H 'authorization: auth' --compressed
Example Response:
Headers: None
status: [0]
Current environment
React: 17.0.2
Axios: 1.2.0
Node: 16.3.0
react-query: 4.2.1
aws-amplify: 5.0.2
API:
ExpressJS: 4.18.2
Node: 14.20.1
Cognito-express: 2.0.19
Cors: 2.8.5
Our React application build is hosted on an AWS S3 bucket with Cloudfront. Our DNS provider is Cloudflare. The API is hosted on AWS through elastic beanstalk. The API is served on NginX.
As far as we can tell, nothing significant has changed in the last several weeks that would cause this issue. (configuration as well as code)
Our API headers:
res.setHeader(
'Access-Control-Allow-Methods',
'GET, POST, OPTIONS, PUT, PATCH, DELETE'
);
res.setHeader(
'Access-Control-Allow-Headers',
'origin,X-Requested-With,content-type'
);
res.setHeader('Access-Control-Allow-Credentials', true);
Our cors settings:
app.use(
cors({
origin: [
'https://examplewebapp1.com',
'https://examplewebapp2.com',
'https://examplewebapp3.com',
],
})
);
React Axios instance:
const instance = axios.create({
baseURL: apiURL,
timeout: 30000,
headers: { 'X-Custom-Header': 'accesstoken', 'timezone': timezone},
});
React Axios request interceptor:
instance.interceptors.request.use(
async (config) => {
try {
const session = await Auth.currentSession();
const accessToken = session.accessToken.jwtToken;
config.headers.authorization = accessToken;
return config;
} catch (err) {
if (err === "No current user")
{
console.log("There is no longer a user.")
handleInactiveUser()
}
captureException(err);
throw err;
}
},
(error) => {
return Promise.reject(error);
},
);
React Axios response interceptor:
instance.interceptors.response.use(
async (resp) => {
// irrelevant code
return resp;
},
(error) => {
// check if the preflight failed
if (typeof error.response === 'undefined') {
// preflight failed
}
if (error.response && error.response.data && error.response.data === 'No Active Account') {
// irrelevant code
}
return Promise.reject(error);
},
);
The actual endpoints that fail are seemingly random. There are quite a few endpoints with failing preflights - we don't see a pattern. (Mostly GET requests).
During debugging on safari with the MacOS debugging tools we've seen this error a few times when we were able to replicate the issue.
[Error] XMLHttpRequest cannot load https://api.example.com due to access control checks.
Here's what we know so far:
The users having these problems are users on IOS (versions 15.3.0 to 16.1.2)
Users are on LTE connection (doesn't occur on Wifi)
Chrome browser and Safari are both having the issue
Occurring on both PWA application and regular browsers
Areas we've looked into:
Removed DDOS protection on Cloudflare (thought Cloudflare was blocking requests)
Verified SSL certificates
Updated Axios from 0.27.2 to 1.2.0
Verified CORS settings
Added origin to the access-control-allow-headers
We've recently added react-query (2 months ago)
Updated our aws-amplify to 5.0.2
Verified our cognito user pool on aws is not rate limiting our authentications

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

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.

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

Authorization Token is not being received by Django server from ios - React native

When I do a request using Fetch (in ios) from a react native app with a Django backend, the header Authorization Token is not being received, but the others headers are.
I try using nc -l 8090 just to check out if the app was correctly sending the header and it was there. Also, this just happens in the ios version, the android one works just fine.
At checking the Django backend, the Authorization Token is not being received ( I printed what i got in the authentication.py file )
I put the fetch code here:
let response = fetch('http://xxx.xxx.x.xxx:8000/' + 'users', {
method: 'GET',
headers: {
'content-type': 'application/json',
'x-app-key': xxxxxxxxxx,
'Authorization': 'Token xxxxxxxxxxxxxxxxxxxx'
}
}).then((response) => {...}
The error message that im getting in the response is "403 django standar not valid authentication".
I'm not using OAuth2. Any other information that could provide, please let me know and thank you for any help.
Something weird that I just noticed is that when I use the nc cmd (lc -l 8000), at the port 8000, it has the same behavior. The authorization token is not being received.
I solved my problem added a slash at the end of the URL.
let response = fetch('http://xxx.xxx.x.xxx:8000/' + 'users/', {
method: 'GET',
headers: {
'content-type': 'application/json',
'x-app-key': xxxxxxxxxx,
'Authorization': 'Token xxxxxxxxxxxxxxxxxxxx'
}
}).then((response) => {...}
I had a variation of this issue with a backend using the laravel framework. I finally resolved it by using the following in the .htaccess file of the public folder.
# Handle Authorization Header
RewriteCond %{HTTP:Authorization} ^(.*)
RewriteRule ^ - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
Combined with one other important factor in my case, losing the trailing slash from the url in the fetch.
Hope this helps, either way. Good luck.

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.

Resources