HTTP headers with Authorize sent as OPTIONS - angularjs

I am making an authentication system based on tokens. When a user logs in a token sent back and this then submitted with each call to the server
Assigning a token
.factory('AuthenticationService', function($rootScope, $http, authService, $httpBackend) {
var service = {
login: function(user) {
$http.post('http://192.168.100.100/myApp/login', { user: user }, { ignoreAuthModule: true })
.success(function (data, status, headers, config) {
$http.defaults.headers.common.Authorization = data.authorizationToken;
console.log("token:" + data.authorizationToken);
authService.loginConfirmed(data, function(config) {
config.headers.Authorization = data.authorizationToken;
return config;
});
})
After this is executed calls are sent as OPTIONS rather than POST the problem being that I am sending to RESTful server and OPTIONS isn't ahhh ummm an option. i.e server expects POST, GET etc.
Chrome shows my headers as ..
General
**Remote Address:** 192.168.100.100:80
**Request URL:** http://192.168.100.100/myapp/login
**Request Method:** OPTIONS
**Status Code:** 404 Not Found
Response Headers
**Access-Control-Allow-Origin:** *
**Cache-Control:** no-cache, must-revalidate
**Connection:** Keep-Alive
**Content-Encoding:** gzip
**Content-Length:** 563
**Content-Type:** text/plain
**Date:** Tue, 04 Aug 2015 04:29:14 GMT
**Expires:** 0
**Keep-Alive:** timeout=5, max=100
**Server:** Apache/2.2.22 (Debian)
**Vary:** Accept-Encoding
**X-Powered-By:** PHP/5.4.41-0+deb7u1
Request Headers
OPTIONS /myapp/login HTTP/1.1
**Host:** 192.168.100.100
**Connection:** keep-alive
**Access-Control-Request-Method:** POST
**Origin:** null
**User-Agent:** Mozilla/5.0 (Linux; U; Android 4.0; en-us; GT-I9300 Build/IMM76D) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30
**Access-Control-Request-Headers:** authorization, content-type
**Accept:** */*
**Accept-Encoding:** gzip, deflate, sdch
**Accept-Language:** en-US,en;q=0.8
Will it always be OPTIONS and do I have to alter my RESTful server to accomodate this, should I not be able to see the token in the headers?

This is an preflight request to check if CORS are enaabled or not
During the preflight request, you should see the following two headers: Access-Control-Request-Method and Access-Control-Request-Headers. These request headers are asking the server for permissions to make the actual request. Your preflight response needs to acknowledge these headers in order for the actual request to work.
Shortly. You need to enable these headers at you server for actual request to work

Can you please check if CORS is enabled? if yes please try to handle OPTIONS request like this
if (req.method === 'OPTIONS') {
console.log('!OPTIONS');
var headers = {};
// IE8 does not allow domains to be specified, just the *
// headers["Access-Control-Allow-Origin"] = req.headers.origin;
headers["Access-Control-Allow-Origin"] = "*";
headers["Access-Control-Allow-Methods"] = "POST, GET, PUT, DELETE, OPTIONS";
headers["Access-Control-Allow-Credentials"] = false;
headers["Access-Control-Max-Age"] = '86400'; // 24 hours
headers["Access-Control-Allow-Headers"] = "X-Requested-With, X-HTTP-Method-Override, Content-Type, Accept";
res.writeHead(200, headers);
res.end();
}

Related

Browser not storing session cookie from React XHR request from express-sessions ** updated config

I am using a React frontend to log into a nodejs server running express-session. Frontend is running on localhost:3000, server is on localhost:5000.
Everything is working properly using postman from localhost (session cookie is sent from server when user is properly authenticated and received/stored by postman. Subsequent postman api request to different path on server uses the session cookie and correctly retrieves the data it should based on the session contents). I can also is login using the browser directly to the server (http://localhost:5000/api/authenticate). The server generates the session, sends the cookie to the browser and it stores the cookie locally.
What doesn't work is when I make the api request from within the React app. The server is returning the session cookie but the browser is not storing it. After researching this for the last few days (there are a lot of questions on this general subject), it seems to be an issue with cross site request but I can't seem to find the right set of app and server settings to get it working properly. The cookie is being sent by the server but the browser won't store it when the request from the app.
*** after some additional troubleshooting and research, I've made some updates. My initial XHR request requires a pre-flight and the request and response headers appear to be correct now but still no cookie being stored in browser. More details below the setup ****
Server Setup
var corsOptions = {
origin: 'http://localhost:3000',
credentials: true
};
app.options('*', cors(corsOptions)) // for pre-flight
app.use(cors(corsOptions));
app.use(session({
genid: (req) => {
console.log('Inside the session middleware');
console.log(req.sessionID);
return uuidv4();
},
store: new FileStore(),
secret: 'abc987',
resave: false,
saveUninitialized: true,
cookie: { httpOnly: false, sameSite: 'Lax', hostOnly: false }
}));
app.use( bodyParser.json() );
app.use(bodyParser.urlencoded({
extended: true
}));
app.use(function(req, res, next) {
res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept, withCredentials, credentials');
next();
});
app.post('/api/authenticate', function(req, res) {
const usernameLower = req.body.username.toLowerCase();
const passwordHash = md5(req.body.password);
connection.query('select USERID from USERS where LOWER(USERNAME)=? && PASSWORD=? ', [usernameLower, passwordHash], function (error, results, fields) {
if (error) {
console.log(error);
req.session.destroy();
res.status(500)
.json({
error: 'Internal error please try again'
});
} else if (results[0]) {
const userId = results[0].USERID;
// setup session data
mySession = req.session;
mySession.user = {};
mySession.user.userId = userId;
res.json(mySession.user);
} else {
console.log('auth failed');
req.session.destroy();
res.status(401)
.json({
error: 'Incorrect email or password'
});
}
});
});
Client setup -- the request is triggered by clicking a submit button in a form
handleSubmit(event) {
event.preventDefault();
axios.defaults.withCreditials = true;
axios.defaults.credentials = 'include';
axios({
credentials: 'include',
method: 'post',
url: 'http://localhost:5000/api/authenticate/',
headers: {'Content-Type': 'application/json' },
data: {
username: this.state.username,
password: this.state.password
}
})
.then((response) => {
if (response.status === 200) {
this.props.setLoggedIn(true);
console.log('userId: '+response.data.userId);
} else {
console.log("login error");
}
})
.catch(error => console.log(error))
}
Below is the response cookie sent to the browser but the browser is not storing it.
{"connect.sid":{"path":"/","samesite":"Lax","value":"s:447935ac-fc08-47c6-9b66-4fa30b355021.Yo5H3XVz3Ux3GjTPVhy8i2ZPJm2RM2RzUnznxU9wBvo"}}
Request headers from XHR request (pre-flight):
OPTIONS /api/authenticate/ HTTP/1.1
Host: localhost:5000
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:78.0) Gecko/20100101 Firefox/78.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Access-Control-Request-Method: POST
Access-Control-Request-Headers: content-type
Referer: http://localhost:3000/
Origin: http://localhost:3000
DNT: 1
Connection: keep-alive
Pre-flight server response headers
HTTP/1.1 204 No Content
X-Powered-By: Express
Access-Control-Allow-Origin: http://localhost:3000
Vary: Origin, Access-Control-Request-Headers
Access-Control-Allow-Credentials: true
Access-Control-Allow-Methods: GET,HEAD,PUT,PATCH,POST,DELETE
Access-Control-Allow-Headers: content-type
Content-Length: 0
Date: Fri, 10 Jul 2020 21:35:05 GMT
Connection: keep-alive
POST request header
POST /api/authenticate/ HTTP/1.1
Host: localhost:5000
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:78.0) Gecko/20100101 Firefox/78.0
Accept: application/json, text/plain, */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Content-Type: application/json
Content-Length: 45
Origin: http://localhost:3000
DNT: 1
Connection: keep-alive
Referer: http://localhost:3000/
Server response headers
HTTP/1.1 200 OK
X-Powered-By: Express
Access-Control-Allow-Origin: http://localhost:3000
Vary: Origin
Access-Control-Allow-Credentials: true
Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept
Content-Type: application/json; charset=utf-8
Content-Length: 95
ETag: W/"5f-Iu5VYnDYPKfn7WPrRi2d2Q168ds"
Set-Cookie: connect.sid=s%3A447935ac-fc08-47c6-9b66-4fa30b355021.Yo5H3XVz3Ux3GjTPVhy8i2ZPJm2RM2RzUnznxU9wBvo; Path=/; SameSite=Lax
Date: Fri, 10 Jul 2020 21:35:05 GMT
Connection: keep-alive
I used the "Will it CORS" tool at https://httptoolkit.tech/will-it-cors/ and my request/response headers all seem to be correct but still no cookie stored.
Pre-flight request contains the correct origin
Pre-flight response contains the correct allow-origin and allow-credentials
POST request contains the correct origin and allow-credentials
POST response contains the correct
Appreciate any help to unravel this....
I solved my issues and wanted to post the solution in case others come across this.
To recap, the backend server is nodejs using express. The following setup allows the front-end to accept the cookies which were created on the nodejs server.
app.use(function (req, res, next) {
res.header("Access-Control-Allow-Origin", "https://frontendserverdomain.com:3000"); // update to match the domain you will make the request from
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
res.header("Access-Control-Allow-Credentials", true); // allows cookie to be sent
res.header("Access-Control-Allow-Methods", "GET, POST, PUT, HEAD, DELETE"); // you must specify the methods used with credentials. "*" will not work.
next();
});
The front-end app is based on React and uses axios to make http request. It is hosted at "https://frontendserverdomain.com:3000" which is added to the "Access-Control-Allow-Origin" header in the nodejs setup (see above).
On the front-end, Axios needs the "withCreditials" setting applied.
axios.defaults.withCredentials = true;
With these settings, your app will be able to exchange cookies with the back-end server.
One gotcha for me getting CORS working was to make sure the front-end host is properly added to the back-end servers header "Access-Control-Allow-Origin". This includes the port number if it's specified in your URL when accessing the front-end.
Inn terms of cookie exchange, the "Access-Control-Allow-Credentials" and "Access-Control-Allow-Methods" headers must be set correctly as shown above. Using a wildcard on "Access-Control-Allow-Methods" will not work.
This does not look right:
axios.defaults.headers.common = {
credentials: "include",
withCredentials: true
}
There are no such request headers. Instead credentials is controlled via XHR request.
Use this instead to make sure your client accepts cookies:
axios.defaults.withCredentials = true;

HTTP headers are not being sent in CORS GET from AngularJS application

My problem is that HTTP headers are not being sent from my AngularJS HTTP GET requests. However, for a HTTP POST, I do see the headers being set. These HTTP requests are over CORS.
Although there are a lot of SO posts on this problem, I have tried them and none of them worked. One of the solutions suggests that HTTP headers are not sent if the data field is empty, and I've tried the suggestion to add an empty data value (which doesn't really sense for a HTTP GET request, by the way), but still, the HTTP headers do not make it.
On a side note, I might defend that this post/question may merit itself as "not a duplicate" (from the other SO posts) as it deals with HTTP GET (as opposed to HTTP POST) and CORS (as opposed to not-CORS).
Here is my technology stack.
NodeJS v4.2.2
Express v4.13.3
AngularJS v1.4.0
To enable CORS, I followed the example here http://enable-cors.org/server_expressjs.html. My NodeJS server application looks like the following.
var express = require('express');
var bodyParser = require('body-parser');
var morgan = require('morgan');
var jwt = require('jsonwebtoken');
var port = process.env.PORT || 8080;
var router = express.Router();
var app = express();
app.set('secret', 'mySecret');
app.use(express.static('public'));
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
app.use(morgan('dev'));
app.use('/api', router);
router.use(function(req, res, next) {
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept, x-access-token');
res.header('Access-Control-Allow-Methods', 'POST, PUT, GET, OPTIONS, DELETE');
next();
});
router.post('/authenticate', function(req, res) {
var username = req.body.username;
var pw = req.body.password;
if(username !== 'root') {
res.json({
success: false,
message: 'User not found'
});
} else if(pw !== 'root') {
res.json({
success: false,
message: 'Password wrong'
});
} else {
var user = {
username: username,
pw: pw
};
var token = jwt.sign(user, app.get('secret'), {
expiresIn: 60 * 60 * 24 * 365
});
res.json({
success: true,
message: 'Enjoy your token!',
token: token
});
}
});
router.use(function(req, res, next) {
/* according to comments, have to ignore OPTIONS request from protection */
if('OPTIONS' === req.method) { next(); return; } //original post modified here to show, after adding this line, the OPTIONS is accessible, then the GET does actually send the required HTTP header
if('/api/authenticate' === req.originalUrl) {
next();
return;
}
var token = req.body.token || req.params['token'] || req.headers['x-access-token'];
if(token) {
jwt.verify(token, app.get('secret'), function(err, decoded) {
if(err) {
return res.json({
success: false,
message: 'Failed to authenticate token'
});
} else {
req.decoded = decoded;
next();
}
})
} else {
return res.status(403).send({
success: false,
message: 'No token provided'
});
}
});
router.get('/users', function(req, res) {
res.json([
{ fname: 'john', lname: 'doe' },
{ fname: 'jane', lname: 'smith' }
]);
})
var server = app.listen(port, function() {
var host = server.address().address;
var port = server.address().port;
console.log('Example app listening at http://%s:%s', host, port);
});
My AngularJS service looks like the following.
myServices.factory('HomeService', ['$resource', '$http', '$location', '$cookies', 'conf', function($resource, $http, $location, $cookies, conf) {
var svc = {};
svc.getRestUrl = function() {
return 'http://localhost:8080';
};
svc.sendData = function(url, data, method) {
var restUrl = svc.getRestUrl() + url;
var options = {
method: method,
url: restUrl,
withCredentials: false
};
var token = $cookies.get('token');
if(_.isEmpty(token)) {
options.headers = {
'X-Requested-With': 'XMLHttpRequest'
};
} else {
options.headers = {
'X-Requested-With': 'XMLHttpRequest',
'x-access-token': token
};
}
if(data) {
options.data = data;
} else {
options.data = '';
}
return $http(options);
}
svc.getData = function(url) {
return svc.sendData(url, null, 'GET');
};
svc.postData = function(url, data) {
return svc.sendData(url, data, 'POST');
};
svc.authenticate = function(username, password) {
var data = JSON.stringify({
username: username,
password: password
});
return svc.postData('/api/authenticate', data);
};
svc.getUsers = function() {
return svc.getData('/api/users');
};
return svc;
}]);
Note
for the service's authenticate method, this is a HTTP POST
for the service's getUsers, this a HTTP GET
when there is no data to send (HTTP GET), the data is set to empty data: ''
Using Fiddler, for authenticate I see the following HTTP request.
POST http://localhost:8080/api/authenticate HTTP/1.1
Host: localhost:8080
Connection: keep-alive
Content-Length: 37
Accept: application/json, text/plain, */*
Origin: http://localhost
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.106 Safari/537.36
Content-Type: application/json;charset=UTF-8
Referer: http://localhost/
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.8
{"username":"root","password":"root"}
For getUsers, I see the following HTTP request.
OPTIONS http://localhost:8080/api/users HTTP/1.1
Host: localhost:8080
Connection: keep-alive
Access-Control-Request-Method: GET
Origin: http://localhost
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.106 Safari/537.36
Access-Control-Request-Headers: accept, x-access-token, x-requested-with
Accept: */*
Referer: http://localhost/
Accept-Encoding: gzip, deflate, sdch
Accept-Language: en-US,en;q=0.8
Am I missing something here with regards to HTTP GET over CORS that the HTTP headers are not being sent?
According to your HTTP request, an OPTIONS request is fired to your CORS API icase of your getUsers method.
When it comes to CORS, there are 2 kinds of requests
Simple Requests
Preflighted Requests
Simple requests
A simple cross-site request is one that meets all the following conditions:
The only allowed methods are:
GET
HEAD
POST
Apart from the headers set automatically by the user agent, the only headers which are allowed to be manually set are:
Accept
Accept-Language
Content-Language
Content-Type
The only allowed values for the Content-Type header are:
application/x-www-form-urlencoded
multipart/form-data
text/plain
Preflighted requests
In case you make any request which violates the conditions of a simple request, then a "preflighted" OPTIONS request is sent in order to determine whether the actual request is safe to send.In particular, a request is preflighted if:
It uses methods other than GET, HEAD or POST. Also, if POST is used
to send request data with a Content-Type other than
application/x-www-form-urlencoded, multipart/form-data, or
text/plain, e.g. if the POST request sends an XML payload to the
server using application/xml or text/xml, then the request is
preflighted.
It sets custom headers in the request (e.g. the request uses a header
such as X-PINGOTHER)
For more details about Preflighted Requests, you can refer to this MDN link.
I believe this is what is happening in your case. Even though you're making a simple GET request, you're adding 2 custom headers X-Requested-With & x-access-token which makes it necessary to validate the safety of your API, so a preflighted OPTIONS request is sent by the browser. The browser will continue with your GET request only if it receives valid response.
In your NodeJS server code, you're handling only POST requests to /authenticate and GET requests to /users, so in case of an OPTIONS request, it's going to the default handler where you're checking for token and if it's not available, you respond with a 403. So I suggest you change your code to handle OPTIONS request as well.

$http.post Request header field Access-Control-Allow-Origin is not allowed by Access-Control-Allow-Headers error

I'm working on a app using Ionic Framework.
On the backend i wrote a Flask Application for api which looks like below:
#API.route("/saverez",methods=["POST","OPTIONS"])
#crossdomain(origin='*', headers="*",methods="*")
#render_api
def saver():
.....
I got errors while posting json to api.
var headers = {
'Access-Control-Allow-Origin' : '*',
'Access-Control-Allow-Methods' : 'POST, GET, OPTIONS',
'Accept': 'application/json'
};
$http({
method: "POST",
headers: headers,
url: url+ '/api/saverez',
data: $scope.form
}).success(function (result)
console.log(result);
}).error(function (data, status, headers, config) {
console.log(data);
console.log(status);
console.log(headers);
console.log(config);
});
So this gives me the error:
XMLHttpRequest cannot load http://myurl/api/saverez. Request header field Access-Control-Allow-Origin is not allowed by Access-Control-Allow-Headers.
I googled it and then i found this snippet:
http://flask.pocoo.org/snippets/56/
I also added headers to my nginx conf like below:
location ~* \.(eot|ttf|woff)$ {
add_header Access-Control-Allow-Origin *;
}
Tried everything in that documentation and also evertyhing i found on google but sadly it didn't do any good.
How can i set the right headers for all origins ? I also use google pagespeed does it can cause this issue ?
Thanks in advance.
--- EDIT ---
Chrome network output
Remote Address:myip
Request URL:http://myurl/api/saverez
Request Method:OPTIONS
Status Code:200 OK
Request Headersview source
Accept:*/*
Accept-Encoding:gzip,deflate,sdch
Accept-Language:en-US,en;q=0.8
Access-Control-Request-Headers:access-control-allow-origin, accept, access-control-allow-methods, content-type
Access-Control-Request-Method:POST
Cache-Control:no-cache
Connection:keep-alive
Host:myurl
Origin:http://192.168.1.46:8100
Pragma:no-cache
Referer:http://192.168.1.46:8100/
User-Agent:Mozilla/5.0 (iPhone; CPU iPhone OS 7_0 like Mac OS X; en-us) AppleWebKit/537.51.1 (KHTML, like Gecko) Version/7.0 Mobile/11A465 Safari/9537.53
Response Headersview source
Access-Control-Allow-Credentials:true
Access-Control-Allow-Headers:*
Access-Control-Allow-Methods:*
Access-Control-Allow-Origin:*
Access-Control-Max-Age:21600
Allow:POST, OPTIONS
Content-Length:0
Content-Type:text/html; charset=utf-8
Date:Thu, 28 Aug 2014 13:26:11 GMT
Server:nginx/1.6.0
In my app module config section I have the following:
angular.module('starterapp', ['ionic'])
.config(function ($stateProvider, $httpProvider, $urlRouterProvider) {
// We need to setup some parameters for http requests
// These three lines are all you need for CORS support
$httpProvider.defaults.useXDomain = true;
$httpProvider.defaults.withCredentials = true;
delete $httpProvider.defaults.headers.common['X-Requested-With'];
}
That is all you need to have to make all the HTTP requests work with CORS. This of course assumes you have made your backend.
You adding those additional headers would not be allowed according the w3c specification for XMLHTTPRequest as they may only be added by the host browser.
PaulT's answer got me very close to solving this problem for myself, but I also had to explicitly add the Content-Type for my post operations.
$httpProvider.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded; charset=UTF-8';
Hope this helps.
I got it working by just adding JSONP as the method
$resource(
'http://maps.google.com/maps/api/geocode/json?address=:address&sensor=false',
{},
{
get: {
method: 'JSONP',
}
});

How and where to implement basic authentication in Kibana 3

I have put my elasticsearch server behind a Apache reverse proxy that provides basic authentication.
Authenticating to Apache directly from the browser works fine. However, when I use Kibana 3 to access the server, I receive authentication errors.
Obviously because no auth headers are sent along with Kibana's Ajax calls.
I added the below to elastic-angular-client.js in the Kibana vendor directory to implement authentication quick and dirty. But for some reason it does not work.
$http.defaults.headers.common.Authorization = 'Basic ' + Base64Encode('user:Password');
What is the best approach and place to implement basic authentication in Kibana?
/*! elastic.js - v1.1.1 - 2013-05-24
* https://github.com/fullscale/elastic.js
* Copyright (c) 2013 FullScale Labs, LLC; Licensed MIT */
/*jshint browser:true */
/*global angular:true */
'use strict';
/*
Angular.js service wrapping the elastic.js API. This module can simply
be injected into your angular controllers.
*/
angular.module('elasticjs.service', [])
.factory('ejsResource', ['$http', function ($http) {
return function (config) {
var
// use existing ejs object if it exists
ejs = window.ejs || {},
/* results are returned as a promise */
promiseThen = function (httpPromise, successcb, errorcb) {
return httpPromise.then(function (response) {
(successcb || angular.noop)(response.data);
return response.data;
}, function (response) {
(errorcb || angular.noop)(response.data);
return response.data;
});
};
// check if we have a config object
// if not, we have the server url so
// we convert it to a config object
if (config !== Object(config)) {
config = {server: config};
}
// set url to empty string if it was not specified
if (config.server == null) {
config.server = '';
}
/* implement the elastic.js client interface for angular */
ejs.client = {
server: function (s) {
if (s == null) {
return config.server;
}
config.server = s;
return this;
},
post: function (path, data, successcb, errorcb) {
$http.defaults.headers.common.Authorization = 'Basic ' + Base64Encode('user:Password');
console.log($http.defaults.headers);
path = config.server + path;
var reqConfig = {url: path, data: data, method: 'POST'};
return promiseThen($http(angular.extend(reqConfig, config)), successcb, errorcb);
},
get: function (path, data, successcb, errorcb) {
$http.defaults.headers.common.Authorization = 'Basic ' + Base64Encode('user:Password');
path = config.server + path;
// no body on get request, data will be request params
var reqConfig = {url: path, params: data, method: 'GET'};
return promiseThen($http(angular.extend(reqConfig, config)), successcb, errorcb);
},
put: function (path, data, successcb, errorcb) {
$http.defaults.headers.common.Authorization = 'Basic ' + Base64Encode('user:Password');
path = config.server + path;
var reqConfig = {url: path, data: data, method: 'PUT'};
return promiseThen($http(angular.extend(reqConfig, config)), successcb, errorcb);
},
del: function (path, data, successcb, errorcb) {
$http.defaults.headers.common.Authorization = 'Basic ' + Base64Encode('user:Password');
path = config.server + path;
var reqConfig = {url: path, data: data, method: 'DELETE'};
return promiseThen($http(angular.extend(reqConfig, config)), successcb, errorcb);
},
head: function (path, data, successcb, errorcb) {
$http.defaults.headers.common.Authorization = 'Basic ' + Base64Encode('user:Password');
path = config.server + path;
// no body on HEAD request, data will be request params
var reqConfig = {url: path, params: data, method: 'HEAD'};
return $http(angular.extend(reqConfig, config))
.then(function (response) {
(successcb || angular.noop)(response.headers());
return response.headers();
}, function (response) {
(errorcb || angular.noop)(undefined);
return undefined;
});
}
};
return ejs;
};
}]);
UPDATE 1: I implemented Matts suggestion. However, the server returns a weird response. It seems that the authorization header is not working. Could it have to do with the fact, that I am running Kibana on port 81 and elasticsearch on 8181?
OPTIONS /solar_vendor/_search HTTP/1.1
Host: 46.252.46.173:8181
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:25.0) Gecko/20100101 Firefox/25.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: de-de,de;q=0.8,en-us;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Origin: http://46.252.46.173:81
Access-Control-Request-Method: POST
Access-Control-Request-Headers: authorization,content-type
Connection: keep-alive
Pragma: no-cache
Cache-Control: no-cache
This is the response
HTTP/1.1 401 Authorization Required
Date: Fri, 08 Nov 2013 23:47:02 GMT
WWW-Authenticate: Basic realm="Username/Password"
Vary: Accept-Encoding
Content-Encoding: gzip
Content-Length: 346
Connection: close
Content-Type: text/html; charset=iso-8859-1
UPDATE 2: Updated all instances with the modified headers in these Kibana files
root#localhost:/var/www/kibana# grep -r 'ejsResource(' .
./src/app/controllers/dash.js: $scope.ejs = ejsResource({server: config.elasticsearch, headers: {'Access-Control-Request-Headers': 'Accept, Origin, Authorization', 'Authorization': 'Basic XXXXXXXXXXXXXXXXXXXXXXXXXXXXX=='}});
./src/app/services/querySrv.js: var ejs = ejsResource({server: config.elasticsearch, headers: {'Access-Control-Request-Headers': 'Accept, Origin, Authorization', 'Authorization': 'Basic XXXXXXXXXXXXXXXXXXXXXXXXXXXXX=='}});
./src/app/services/filterSrv.js: var ejs = ejsResource({server: config.elasticsearch, headers: {'Access-Control-Request-Headers': 'Accept, Origin, Authorization', 'Authorization': 'Basic XXXXXXXXXXXXXXXXXXXXXXXXXXXXX=='}});
./src/app/services/dashboard.js: var ejs = ejsResource({server: config.elasticsearch, headers: {'Access-Control-Request-Headers': 'Accept, Origin, Authorization', 'Authorization': 'Basic XXXXXXXXXXXXXXXXXXXXXXXXXXXXX=='}});
And modified my vhost conf for the reverse proxy like this
<VirtualHost *:8181>
ProxyRequests Off
ProxyPass / http://127.0.0.1:9200/
ProxyPassReverse / https://127.0.0.1:9200/
<Location />
Order deny,allow
Allow from all
AuthType Basic
AuthName “Username/Password”
AuthUserFile /var/www/cake2.2.4/.htpasswd
Require valid-user
Header always set Access-Control-Allow-Methods "GET, POST, DELETE, OPTIONS, PUT"
Header always set Access-Control-Allow-Headers "Content-Type, X-Requested-With, X-HTTP-Method-Override, Origin, Accept, Authorization"
Header always set Access-Control-Allow-Credentials "true"
Header always set Cache-Control "max-age=0"
Header always set Access-Control-Allow-Origin *
</Location>
ErrorLog ${APACHE_LOG_DIR}/error.log
</VirtualHost>
Apache sends back the new response headers but the request header still seems to be wrong somewhere. Authentication just doesn't work.
Request Headers
OPTIONS /solar_vendor/_search HTTP/1.1
Host: 46.252.26.173:8181
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:25.0) Gecko/20100101 Firefox/25.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: de-de,de;q=0.8,en-us;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Origin: http://46.252.26.173:81
Access-Control-Request-Method: POST
Access-Control-Request-Headers: authorization,content-type
Connection: keep-alive
Pragma: no-cache
Cache-Control: no-cache
Response Headers
HTTP/1.1 401 Authorization Required
Date: Sat, 09 Nov 2013 08:48:48 GMT
Access-Control-Allow-Methods: GET, POST, DELETE, OPTIONS, PUT
Access-Control-Allow-Headers: Content-Type, X-Requested-With, X-HTTP-Method-Override, Origin, Accept, Authorization
Access-Control-Allow-Credentials: true
Cache-Control: max-age=0
Access-Control-Allow-Origin: *
WWW-Authenticate: Basic realm="Username/Password"
Vary: Accept-Encoding
Content-Encoding: gzip
Content-Length: 346
Connection: close
Content-Type: text/html; charset=iso-8859-1
SOLUTION:
After doing some more research, I found out that this is definitely a configuration issue with regard to CORS. There are quite a few posts available regarding that topic but it appears that in order to solve my problem, it would be necessary to to make some very granular configurations on apache and also make sure that the right stuff is sent from the browser.
So I reconsidered the strategy and found a much simpler solution. Just modify the vhost reverse proxy config to move the elastisearch server AND kibana on the same http port. This also adds even better security to Kibana.
This is what I did:
<VirtualHost *:8181>
ProxyRequests Off
ProxyPass /bigdatadesk/ http://127.0.0.1:81/bigdatadesk/src/
ProxyPassReverse /bigdatadesk/ http://127.0.0.1:81/bigdatadesk/src/
ProxyPass / http://127.0.0.1:9200/
ProxyPassReverse / https://127.0.0.1:9200/
<Location />
Order deny,allow
Allow from all
AuthType Basic
AuthName “Username/Password”
AuthUserFile /var/www/.htpasswd
Require valid-user
</Location>
ErrorLog ${APACHE_LOG_DIR}/error.log
</VirtualHost>
Here is a perfect solution:
https://github.com/fangli/kibana-authentication-proxy
Support not only basicAuth backend, but also GoogleOAuth and BasicAuth for the client.
If works, please give a star, thanks.
In Kibana, replace the existing elastic-angular-client.js with the latest which can be found here. Then, in the Kibana code replace all instances of:
$scope.ejs = ejsResource(config.elasticsearch);
with
$scope.ejs = ejsResource({server: config.elasticsearch, headers: {'Access-Control-Request-Headers': 'accept, origin, authorization', 'Authorization': 'Basic ' + Base64Encode('user:Password')}});
That should be all you need.
Update:
Is apache configured for for CORS? See this.
Header always set Access-Control-Allow-Methods "GET, POST, DELETE, OPTIONS, PUT"
Header always set Access-Control-Allow-Headers "Content-Type, X-Requested-With, X-HTTP-Method-Override, Origin, Accept, Authorization"
Header always set Access-Control-Allow-Credentials "true"
Header always set Cache-Control "max-age=0"
Header always set Access-Control-Allow-Origin *
You are correct in that it's a CORS issue. Kibana 3 uses CORS to communicate with ElasticSearch.
In order to enable HTTP Authentication Headers and Cookies to be sent with the Kibana CORS requests you need to do two things:
ONE: In your Kibana config.js file, find the setting where your ElasticSearch server is defined:
elasticsearch: "http://localhost:9200",
This needs to be changed to:
elasticsearch: {server: "http://localhost:9200", withCredentials: true},
This will tell Kibana to send the Authentication headers and cookies IF the server is capable of received them.
TWO: Next you need to go into your ElasticSearch config file (elasticsearch.yml on the host server; mine was located at /etc/elasticsearch/elasticsearch.yml on a CentOS7 server). In this file you will find a "Network And HTTP" section. You will need to find the line that says:
#http.port: 9200
Uncomment this line and change the port to the port that you want ElasticSearch to run on. I chose 19200. Then do the same for the #transport.tcp.port: 9300 setting. Again I chose 19300.
Lastly, at the end of this section (just for organizational sake, you could also simply append the following to the end of the file) add in:
http.cors.allow-origin: http://localhost:8080
http.cors.allow-credentials: true
http.cors.enabled: true
You can change the above origin address to wherever your web server is serving Kibana from. Alternatively you could simply put /.*/ to match all origins but this is not adviseable.
Now save the elasticsearch.yml file and restart the elasticsearch server. Your reverse proxy should be configured to run on port 9200 and point to 19200 if the request authenticates.
Word of warning, if you are using Cookies to authenticate requests you should make sure to white list the HTTP OPTIONS method in your reverse proxy configuration as only GET, PUT, POST and DELETE requests include the cookies. I haven't tested whether OPTIONS include the Authentication header as well but it may be the same situation as the cookies. Kibana will not function correctly if the OPTIONS requests cannot get through as well.
It is also a good idea to configure your reverse proxy to blacklist any request that ends in _shutdown as this command shouldn't be needed via external requests in most cases.

Why does a cross-domain angularjs $http.post request fail when a XMLHttpRequest succeeds?

I am trying to exercise the Trello API with an application key and token from an angular (version 1.0.5) webapp. The server seems correctly configured to handle CORS. A test request with http://test-cors.org from enable cors works as expected.
When I do a post request in one of my angular controllers:
$http.post(url).success(function(data) {
$scope.server_resp = data;
});
I get a Request header field Content-Type is not allowed by Access-Control-Allow-Headers error. (Even though, as you see below, the Access-Control-Allow-Origin is set to '*'). Why is this header added and can it be removed?
XMLHttpRequest
When I make the same request using raw XMLHttpRequest, it succeeds. Here are the headers for the XMLHttpRequest:
Request Method:POST
Status Code:200 OK
Accept:*/*
Accept-Charset:ISO-8859-1,utf-8;q=0.7,*;q=0.3
Accept-Encoding:gzip,deflate,sdch
Accept-Language:en-US,en;q=0.8
Connection:keep-alive
Content-Length:0
Host:api.trello.com
Origin:http://192.168.0.125:9000
Referer:http://192.168.0.125:9000/
User-Agent:Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.22 (KHTML, like Gecko) Chrome/25.0.1364.172 Safari/537.22
Response
Access-Control-Allow-Methods:GET, PUT, POST, DELETE
Access-Control-Allow-Origin:*
Cache-Control:max-age=0, must-revalidate, no-cache, no-store
Content-Length:563
Content-Type:application/json
Date:Mon, 18 Mar 2013 02:49:37 GMT
Expires:Thu, 01 Jan 1970 00:00:00
X-Powered-By:Express
X-Server-Time:1363574977568
Angular $http.post
Here are the headers for the angular initiated request. Note that the browser made a pre-flight OPTIONS request:
Request Method:OPTIONS
Status Code:200 OK
Accept:*/*
Accept-Charset:ISO-8859-1,utf-8;q=0.7,*;q=0.3
Accept-Encoding:gzip,deflate,sdch
Accept-Language:en-US,en;q=0.8
Access-Control-Request-Headers:accept, origin, content-type
Access-Control-Request-Method:POST
Connection:keep-alive
Host:api.trello.com
Origin:http://192.168.0.125:9000
Referer:http://192.168.0.125:9000/
User-Agent:Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.22 (KHTML, like Gecko) Chrome/25.0.1364.172 Safari/537.22
Response
Access-Control-Allow-Methods:GET, PUT, POST, DELETE
Access-Control-Allow-Origin:*
Content-Length:0
Content-Type:text/html; charset=utf-8
Date:Mon, 18 Mar 2013 02:51:00 GMT
X-Powered-By:Express
Is there a way to configure angular's request headers to allow the above $http.post() code to work?
The 'content-type' header is not accepted by the server and is added by default for Angular $http POST request (see $http doc). You can try to remove it from your $http config. Inject $httpProvider in your controller, then this might work:
delete $httpProvider.defaults.headers.post['Content-type']
You might have to try with 'content-type' also, I'm not sure of the case to use.
Add the headers param to the $http and you'll be fine.
var config = {
method: 'POST',
url: 'your url',
headers: {
'Content-Type': undefined
},
data: {
"channel": "#fun-and-game",
"username": $scope.question.title,
"text": $scope.question.text,
"icon_emoji": ":ghost:"
},
};
$http(config).success(function(data) {
$scope.server_resp = data;
}).error(function(response) {
});
for more info, check angularjs $http docs
As per this angular pull request, CORS can be made to work by deleting X-Requested-With which causes a pre-flight OPTIONS request:
App.config(['$httpProvider', function($httpProvider) {
delete $httpProvider.defaults.headers.common["X-Requested-With"];
}
Note that I have not tried this personally, but a co-worker had to deltete the header to make his CORS request work.
I just ran into a similar issue and the problem was that I was getting the url wrong. I was posting to 1/cards/actions/createCard because I missread the docs. I got an access-control related error even though headers etc. look right. Posting to 1/cards created a card, which is what I wanted.
This worked for me
$http({
method : "POST",
url : url,
data : $.param({key: 'value', key2 : 'value'}),
headers : { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' }
})
To avoid this issue, create a function in the server side to catch the 'OPTIONS' and just return true. Some thing as follows.
/**
* #url OPTIONS /
*/
public function options()
{
return;
}

Resources