CORS with Akka-Http and Spray - javascript

I'm trying to send http requests from a local file (client) to my backend server.
After reading countless articles on how to enable CROS (cross-origin-resource-sharing), I'm still getting the error: "Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'null' is therefore not allowed access. The response had HTTP status code 405."
For my backend server, I use Akka-Http and Spray-Json. As a result, I decided to use akka-http-cors (https://github.com/lomigmegard/akka-http-cors), but that didn't seem to solve the problem either. I understand that I should be using the options directive and 'Access-Control-Allow-Origin'(fileName), but I can't seem to figure out how to use them correctly.
I've attached snippets of my backend and javascript code. If anyone knows how to properly enable CROS between my client and server that would be amazing.
Backend scala-akka-spray code
var signInUrl = 'http://0.0.0.0:8080/user/sign-in';
function sendEntry(form, signType) {
var jsonString = serializeEntry(form);
var httpRequest = new XMLHttpRequest();
httpRequest.open('POST', signInUrl, true); // true meanining asynchronous
httpRequest.setRequestHeader('Content-type', 'application/json');
httpRequest.send(jsonString);
}

I was able to get this working through the code listed at https://dzone.com/articles/handling-cors-in-akka-http
Copied here for completion:
import akka.http.scaladsl.model.HttpMethods._
import akka.http.scaladsl.model.headers._
import akka.http.scaladsl.model.{HttpResponse, StatusCodes}
import akka.http.scaladsl.server.Directives._
import akka.http.scaladsl.server.directives.RouteDirectives.complete
import akka.http.scaladsl.server.{Directive0, Route}
import scala.concurrent.duration._
/**
* From https://dzone.com/articles/handling-cors-in-akka-http
*
*
*/
trait CORSHandler {
private val corsResponseHeaders = List(
`Access-Control-Allow-Origin`.*,
`Access-Control-Allow-Credentials`(true),
`Access-Control-Allow-Headers`("Authorization",
"Content-Type", "X-Requested-With"),
`Access-Control-Max-Age`(1.day.toMillis)//Tell browser to cache OPTIONS requests
)
//this directive adds access control headers to normal responses
private def addAccessControlHeaders: Directive0 = {
respondWithHeaders(corsResponseHeaders)
}
//this handles preflight OPTIONS requests.
private def preflightRequestHandler: Route = options {
complete(HttpResponse(StatusCodes.OK).
withHeaders(`Access-Control-Allow-Methods`(OPTIONS, POST, PUT, GET, DELETE)))
}
// Wrap the Route with this method to enable adding of CORS headers
def corsHandler(r: Route): Route = addAccessControlHeaders {
preflightRequestHandler ~ r
}
// Helper method to add CORS headers to HttpResponse
// preventing duplication of CORS headers across code
def addCORSHeaders(response: HttpResponse):HttpResponse =
response.withHeaders(corsResponseHeaders)
}
Using it as:
private val cors = new CORSHandler {}
val foo: Route = path("foo") {
//Necessary to let the browser make OPTIONS requests as it likes to do
options {
cors.corsHandler(complete(StatusCodes.OK))
} ~ post( cors.corsHandler(complete("in foo request")) )
}
More details: https://ali.actor/enabling-cors-in-akka-http/

May be useful for someone: I just added a cors() directive and it did the trick for me:
import ch.megard.akka.http.cors.scaladsl.CorsDirectives.cors
val route: Route = cors() { // cors() may take optional CorsSettings object
get {...}
}

Related

Fetch problem with node.js express server

I'm having some trouble with the fetch and node.js.
In my frontend when i click a button, i would like to send a post request in order to receive an array from my backend as answer. I'n my backend i'm using node.js with express, in my frontend i'm using the fetch function.
The error that occours is the following:
Access to fetch at 'http://localhost:8080/api' from origin 'real localhost address' has been blocked by CORS policy: 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.
Code Here
const getArray = async() => {
const data = await fetch ("http://localhost:8080/api");
const dataJson = await data.json();
console.log(dataJson)
}
getArray();
In my server i've got
app.post("/api", (req,res) => {
res.sendFile(JSON.stringify(arr));
});
You need to add request options. Please refer to the MDN docs for further information.
As #Kudah said, you should read the docs.
Fetch (and XMLHttpRequest) follow the same-origin policy. This means that browsers restrict cross-origin HTTP requests from within scripts. A cross-origin request occurs when one domain (for example http://example2.com/) requests a resource from a separate domain (for example http://example1.com/).
The easiest way to solve this, (If you don't want to dig too much into this)
const whiteList = [ "https://myRealBackendUrl-1", "https://myRealBackendUrl-2" ];
// you can also pass a string here instead here instead of array
const corsOptions = {
credentials: true,
origin: process.env.NODE_ENV !== production ? "http://localhost:4000" : whiteList
// if you are in a dev environment, you probably want something like localhost
// http://localhost:4000 is just a demo backend. replace it with your own.
// if you are in a production environment, for example heroku then your backend
// url will be something like http://example.herokuapp.com
// in that case `const whiteList = [ "http://example.herokuapp.com" ];`
};
app.use(cors(corsOptions));
The above code should be enough for the normal use case.
There is also callback function, it is if you want to run some function of your own. Don't read it if you dont plan to use any dynamic checking
var corsOptionsDelegate = async (req, callback) => {
var corsOptions = { origin: false };
try {
// you can do some dynamic check here
// For example: check database for some conditions then allow access
if( myDatabaseSays == true ) corsOptions.origin = true;
else corsOptions.origin = false;
} catch (err) {
console.log(err);
// corsOptions.origin = false;
}
callback(null, corsOptions) // chain it
}
Anyway read the docs properly for more info
[1]: https://expressjs.com/en/resources/middleware/cors.html

Axios request receiving a CORS error only when inside a callback/async function

I'm using VueJS for an app I am building. The server I have is written in Golang and has been set to accept CORS. In the app, in one of my components, searchBar, I have set it to fetch some data before it is created.
var searchBar = {
prop: [...],
data: function() {
return { ... };
},
beforeCreate: function() {
var searchBar = this;
axios.request({
url: '/graphql',
method: 'post',
data: {
'query': '{courses{id, name}}'
}
})
.then(function(response) {
searchBar.courses = response.data.data.courses;
});
},
methods: { ... },
template: `...`
};
Axios works perfectly here. It gets the data I need. searchBar has a button which causes it to emit an event which is then picked up by another component, searchResults. Upon receiving the event, searchResults will fetch some data.
var searchResults = {
data: function() {
return { ... }
},
mounted: function() {
var sr = this;
this.$bus.$on('poll-server', function(payload) {
var requestData = {
url: '/graphql',
method: 'post',
data: { ... },
...
};
...
axios.request(requestData)
.then(function(response) {
console.log(response);
}
);
});
},
template: `...`
};
Note that my Axios request call is now inside a callback function. When this call is performed, I receive a CORS error:
Access to XMLHttpRequest at 'http://127.0.0.1:9000/graphql' from origin 'http://127.0.0.1:8080' 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.
My server is located at http://127.0.0.1:9000, with the client in http://127.0.0.1:8080. Here is the content of the OPTIONS request of the second request call.
For comparison, here is the request header of the first request call (that works!).
I have already set my Golang server to support CORS via go-chi/cors. This is how set it up.
router := chi.NewRouter()
...
// Enable CORS.
cors := cors.New(cors.Options{
AllowedOrigins: []string{"*"},
AllowedMethods: []string{"POST", "OPTIONS"},
AllowedHeaders: []string{"Accept", "Authorization", "Content-Type", "X-CSRF-Token"},
ExposedHeaders: []string{"Link"},
AllowCredentials: true,
MaxAge: 300,
})
router.Use(
render.SetContentType(render.ContentTypeJSON),
middleware.Logger,
middleware.DefaultCompress,
middleware.StripSlashes,
middleware.Recoverer,
cors.Handler,
)
router.Post("/graphql", gqlServer.GraphQL())
return router, db
What is causing the error I am having and how can it be solved?
This CORS error is expected. The CORS plugin you are using does request filtering for you. If you look at the list of allowed headers, you can see it's missing the header called snb-user-gps-location that you are trying to send in your axios call.
Either add that header to the allowed list, or don't send it from the front end.
I still suspect the go-chi CORS setup. I would suggest looking at setting up CORS by hand. It's not that difficult. This page will get a basic setup: https://flaviocopes.com/golang-enable-cors/
If that works with your nested API setup, we can then work backwards to determine the go-chi config issue.
Update:
I would also investigate the other middleware steps - commenting out all non-essential ones.
Middleware handlers normally inspect the r *http.Request or write headers to the w http.ResponseWriter and then the final handler will write to the response body. But throughout the middleware chain the following header/body write flow should look like one of these two flows:
Success:
w.Header().Set(...) // headers
w.Write(...) // body
Note the above flow will issue an implicit http status code write, to keep headers appearing first and body second:
w.Header().Set(...) // headers
w.WriteHeader(http.StatusOK) // implicit success http status code
w.Write(...) // body
Failure:
In the event of reporting a runtime error, the flow should be:
w.Header().Set(...) // headers
w.WriteHeader(http.StatusInternalServerError) // some unrecoverable error
w.Write(...) // optional body
The reason I bring this up, I've seen 3 types of bugs which mess up this flow:
bad middleware handlers write headers after the body causing client confusion
calling http.Error thinking that stops the API dead - instead of returning immediately after the http.Error and ensuring no subsequent middleware handlers are called
write the same header twice. Rewriting headers in a subsequent handler will cause the client to see the last version (thus clobbering any previous versions)
So to fully trace things, I would log.Println all header/body writes for your API to ensure the above flow is correct and no intended values are being overwritten.

How can I get the Cookies' `csrftoken` in `Axios.interceptors.request`'s config?

How can I get the Cookies' csrftoken in Axios.interceptors.request's config?
Axios.interceptors.request.use(
config => {
if (
config.method === "post" ||
config.method === "put" ||
config.method === "delete"||
config.method === "get"
) {
}
if (Cookies.get('token')!==undefined) {
config.headers['Authorization']= 'Token '+Cookies.get('token');
}
// there I try to get the `csrftoken` in the Cookies, but I can not get.
if (Cookies.get('csrftoken')!==undefined) {
config.headers['x-csrftoken']= Cookies.get('csrftoken'); // 'CSRFToken'
}
return config;
},
error => {
return Promise.reject(error.data.error.message);
}
);
In the Axios.interceptors.request's config I can not get Cookies's csrftoken: Cookies.get('csrftoken').
my AxiosConfig code is bellow:
AxiosConfig:{
baseURL: 'http://10.10.10.105:8001/',
responseType: "json",
withCredentials: true, // there will send the Cookie (with it there are: sessionid, csrftoken)
xsrfCookieName: 'csrftoken', // default: XSRF-TOKEN
xsrfHeaderName: 'x-csrftoken', // default: X-XSRF-TOKEN
headers: {
"Content-Type": "application/json;charset=utf-8"
}
}
edit-1
There is the csrftoken in the Cookie.
edit-2
And in the cookie, there is no csrftoken too.
edit-3
if I get the document.cookie in console, I will get the "":
```
document.cookie
< ""
```
edit-4
in my Django backend, the settings.py:
INSTALLED_APPS:
...
'corsheaders',
'rest_framework',
'rest_framework.authtoken',
'rest_framework_docs',
'rest_auth',
'allauth',
'allauth.account',
'allauth.socialaccount',
'rest_auth.registration',
...
I am not sure whether the rest_auth and allauth will affect the csrftoken.
First of all, always make sure that the cookie is not flagged as httpOnly.
If it is, your javascript code won't be able to read / modify its content.
You can check the cookies tab in your browser and you will see whether it's readable or not.
In in your case though, django shouldn't be setting the flag as httpOnly as the docs describe how to read the value in javascript directly from the cookie.
A few things I can point out by experience:
The config object might not yet be filled with the data when you receive it within an interceptor. Therefore setting config.headers = ...; might trigger an error.
Make sure you write:
config.headers = config.headers || {};
before setting the headers so no 'config.headers is undefined' will be triggered.
Alternatively to directly reading the cookie, store the csrf value inside an hidden input as per default procedure.
You can do something like this (syntax might be incorrect):
<input type="hidden"
name="csrftoken"
value="{% csrf_token %}"/>
</div>
and then send the token within the interceptor with:
config.headers['x-csrftoken'] = document.querySelector('input[name="csrftoken"]').value;
In such case, since you do not need to read the cookie, it would be a huge plus to set it as httpOnly.
Axios has an order of precedence to config settings, so it could be possible that settings are overwritten by other code.
For example in Chrome browser, you can open the dev tools pane, click the Applications tab, and then on the left-side, you can click Cookies to view those to make sure the correct ones exist:
If you have an Axios request interceptor that defines the XSRF-TOKEN header, it would be overwritten by this:
axios.post(route('logout'), undefined, {
headers: {
'XSRF-TOKEN': 'bad',
},
});
If you have a file, such as you would in many Laravel/Vue projects, called bootstrap.js or axios.js that declares global config settings, that setting could be overwritten by an interceptor.
For example:
// document head
<meta name="csrf-token" content="{{ csrf_token() }}">
...
// axios.js or bootstrap.js (imported in SPA app's main.js file)
window.axios = require('axios');
window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
const token = document.head.querySelector('meta[name="csrf-token"]');
if (token) {
// an interceptor would overwrite this due to right-to-left precedence
window.axios.defaults.headers.common['XSRF-TOKEN'] = token.content;
} else {
console.error('CSRF token not found: https://laravel.com/docs/csrf#csrf-x-csrf-token');
}
That last example there is very important because, that would be used for calling internal APIs. If you are calling an external API that utilizes CORS, such as Google, they can require specific headers and other headers can cause errors to be thrown.
To temporarily overwrite headers, you can use a pattern such as this where you are calling the external API:
// temporarily wipe 'XSRF-TOKEN' header for Google CORS
const instance = axios.create();
instance.defaults.headers.common = {};
instance.defaults.headers.common.accept = 'application/json';
const { data } = await instance.get('https://www.external.com/foobars');
That would overwrite an interceptor as well because instance by design takes precedence over global defaults.
Here is the request interceptor that I use:
import Cookies from 'js-cookie';
/**
* Request interceptor: for each request to the server,
* attach the CSRF token if it exists.
*
*/
axios.interceptors.request.use(async (request) => {
try {
const csrf = Cookies.get('XSRF-TOKEN');
request.withCredentials = true;
if (csrf) {
request.headers.common['XSRF-TOKEN'] = csrf;
}
return request;
} catch (err) {
throw new Error(`axios# Problem with request during pre-flight phase: ${err}.`);
}
});
Since CSRF requires to be accessible by JavaScript, it must not be httpOnly, so rather than using a library such as js-cookie, you can get the cookie using document.cookie, but that returns a semicolon-delimited string of key/value pairs, so you could exercise your mind by making a function that extracts the CSRF.
console.log('cookies', document.cookie)
Bonus reading:
https://developer.mozilla.org/en-US/docs/Web/API/Document/cookie
https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/cookies/get
https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/cookies/set

Dojo rpc.JsonService - set custom header

Dojo v1.6.0.
Is there any way to set custom header (spring csrf protection in my case) to every call for all instances of dojo.rpc.JsonService()?
Or at least to every call for specific instances of dojo.rpc.JsonService()?
Problem is in back-end Spring 4 csrf protection which filters everything without specific header in request and returns HTTP 403 Forbidden status.
For now my code looks like:
...
dojo.require("dojo.rpc.RpcService");
dojo.require("dojo.rpc.JsonService");
var myService = new dojo.rpc.JsonService("someMyService");
var result = myService.myRemoteMethod(param1, param2, ... );
...
For example for jQuery code which handles every ajax request and set header to it looks like:
var token = $("meta[name='_csrf']").attr("content");
var header = $("meta[name='_csrf_header']").attr("content");
$(document).ajaxSend(function (e, xhr, options) {
xhr.setRequestHeader(header, token);
});
It would be perfect to make something like that for dojo.
I haven't found any solution for dojo 1.6, but found that I can fix this problem with handling every ajax request with pure javascript as explained here
So my final solution is:
(function(send) {
var token = $("meta[name='_csrf']").attr("content");
var header = $("meta[name='_csrf_header']").attr("content");
XMLHttpRequest.prototype.send = function(data) {
if (isNotBlank(token) && isNotBlank(header)) {
this.setRequestHeader(header, token);
}
send.call(this, data);
};
})(XMLHttpRequest.prototype.send);

superagent set custom request headers es6 not Access-Control-Request-Headers

I know this is addressed in this post but I am still having trouble setting a custom header using ES6 and am wondering if anyone has run into this issue? The problem is when is set the header using .set I only set the Access-Control-Request-Header to the label I want to set it and the value is lost. I want to set a custom field on the request header using superagent and not sure how.
Let's say I am running this in my app (client)
import ajax from 'superagent'
ajax.get(baseURL + "/query/")
.query({q: "SELECT Id FROM USER WHERE Id=" + id})
.set('X-Authorization', 'Oauth ' + token)
.set('Content-Type', 'application/json')
.end((error, response) => {
if(errro) { console.log(error); }
}
the header the get request makes contains:
Access-Control-Request-Headers:content-type, x-authorization
under Request Headers in the network tab of the browser debugger. I want to set the headers of the get so that under Request Headers in the network tab of the browser dubugger I see:
X-Authorization: some_token
Content-Type: application/json
Does anyone have any ideas on how I can set the Request Headers to have any field/value I want using ES6 and superagent?
thanks to all in advanced!
try adding the following code to your script, before get is called.
ajax._defaultHeaders = {};
function isObject(obj) { return Object(obj) === obj; };
ajax.set = (function (field, value) {
if (isObject(field)) {
for(var key in field) this.set(key, field[key]);
return this;
}
this._defaultHeaders[field] = value;
return this;
}).bind(ajax)
used to have similar problem with Spring Boot application and React JS on frontend.
When I dump headers that were attached to RQ I used to see only headers with pattern:
Access-Control-Request-Headers:content-type, x-authorization, etc...
however that was problem connected with my backend application, NOT with superagent on frontend.
I had to turn on cors() in WebSecurityConfig to look similar to this one:
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity.csrf().disable()
.cors().and()
.authorizeRequests().antMatchers("/auth/login", "/auth/register").permitAll().
anyRequest().authenticated().and().
exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint).and().sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
so as you can see problem was with Spring configuration, not with Superagent,
Regards
R.

Categories