Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Configure Angularjs and CORS correctly using Apache running Flask api and Angularjs frontend on separate domain

This has been discussed all through Stackoverflow but I have not been able to find the solution for getting this to work. I even tried following alot of what was discussed in this post (Issues with CORS. Flask <-> AngularJS) since it was closly related to my setup, but I am still pretty much lost.

I have a python flask api server running locally. I have named this domain bac-api. Here is what this vhost looks like.

<VirtualHost *:80>
    ServerName bac-api

    WSGIDaemonProcess bacapi python-path=/home/lumberjacked/workspace/bas/development/lib/python2.7/site-packages
    WSGIScriptAlias / /home/lumberjacked/workspace/bas/development/bac-api/bacapi.wsgi
    Header set Access-Control-Allow-Origin "*"
    Header set Access-Control-Allow-Headers "Origin, X-Requested-With, Content-Type, Accept, application/json"
    Header set Access-Control-Allow-Methods "POST, GET, OPTIONS"

    <Directory /home/lumberjacked/workspace/bas/development/bac-api>
        WSGIProcessGroup bacapi
        WSGIApplicationGroup %{GLOBAL}
        Order deny,allow
        Allow from all
    </Directory>

#SSLEngine on
#SSLCertificateFile /etc/apache2/ssl/bac_api_ssl.crt
#SSLCertificateKeyFile /etc/apache2/ssl/bac_api_ssl.key
</VirtualHost>

I have an angularjs site running as my front-end on a separate domain called manage.bac. I am writing an authentication service that connects to the bac-api and posts an api key and/or user credentials asking the server if the user is authenticated or not.

I can curl over to the server and this works and returns a response.

curl -X POST -H "Content-Type: application/json" -d '{ "api": "7f3d6d7338b921ae4ca95cecc452ef1790005ec10c2343fabe5a59ea" }' http://bac-api/authenticated/  

response -- {"authenticated": false}

Also going to manage.bac and placing my login creditials [email protected] and password I get the same response but also I have configured it to return a 401 if you are not authenticated with a stored session.

POST http://bac-api/authenticated/ 401 (UNAUTHORIZED) bac-api/authenticated/:1

The problems start when I switch over to ssl certs and port 443. So If on both domains I switch the vhost to port 443 and uncomment the ssl certs. After restarting apache there are no errors and the domains work as far as not showing any kind of 500 error or anything like that. I can curl over to the bac-api server using https and get a response.

curl -k -X POST -H "Content-Type: application/json" -d '{ "api": "7f3d6d7338b921ae4ca95cecc452ef1790005ec10c2343fabe5a59ea" }' https://bac-api/authenticated/

response -- {"authenticated": false}

But as soon as I go to https://manage.bac and redirect to the login page, all the trouble seems to start.

As soon as I click inside the login fields that console throws this error and I do not know if it is related or not.

Blocked a frame with origin "https://manage.bac" from accessing a frame with origin "chrome-extension://hdokiejnpimakedhajhdlcegeplioahd".  The frame requesting access has a protocol of "https", the frame being accessed has a protocol of "chrome-extension". Protocols must match.

Here is my angularjs authentication code with the console.log methods outputting some stuff.

$http.post('https://bac-api/login/', user)
            .success( function( user ) {
                console.log('success');
                console.log(user);                     
            })
            .error( function( error ) {
                console.log('error');
                console.log(error);
            });

Object {email: "[email protected]", password: "password"} authentication-service.js:36
error authentication-service.js:50
                  authentication-service.js:51

When this error comes back there isn't anything in the response at all. I can see inside the network tab of chrome tools that this request is getting pre-flighted because it is sending an OPTIONS over to the server.

OPTIONS /login/ HTTP/1.1
Host: bac-api
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:24.0) Gecko/20100101 Firefox/24.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Origin: https://manage.bac
Access-Control-Request-Method: POST
Access-Control-Request-Headers: content-type,x-requested-with
Connection: keep-alive
Pragma: no-cache
Cache-Control: no-cache

Also I can see in the Network tab it is showing up as an OPTIONS and it has been cancelled. Which I do not know what that means.

enter image description here

I have configured inside angularjs with these options but it doesn't seem to make a difference on anything.

config(['$httpProvider', function($httpProvider) {
        $httpProvider.defaults.useXDomain = true;
        delete $httpProvider.defaults.headers.common['X-Requested-With'];

    }
]);

Also inside my flask application tried implementing the decorator mentioned here http://flask.pocoo.org/snippets/56/ and also mentioned in other stackoverflow posts. This doesn't seem to do anything either. Also I assumed that because I put inside my vhost the access headers for the api server that I really didn't need to do the same thing in the app. Though I could be totally wrong because I dont have a clue on what I'm doing.

Any help is really appreciated. I am sorry if this seems like a duplicate issue but so far I cannot get this to work and I have tried every post on stackoverflow that I can to solve this issue.

--- Edit with updated information

This morning as an experiment I switched both vhosts back over to port 80 and commented out all Allow access headers. Restarted and tried logging in again, here are my results.

Failed to load resource: Origin http://manage.bac is not allowed by Access-Control-Allow-Origin. http://bac-api/authenticated/
XMLHttpRequest cannot load http://bac-api/authenticated/. Origin http://manage.bac is not allowed by Access-Control-Allow-Origin. manage.bac/:1

So after this error I went inside my bac-api vhost and un-commented Header set Access-Control-Allow-Origin "*" and re-started again and tried logging in. Here are the new errors that display.

OPTIONS http://bac-api/authenticated/ Request header field Content-Type is not allowed  by Access-Control-Allow-Headers. angular.js:9312
(anonymous function) angular.js:9312
sendReq angular.js:9146
$http angular.js:8937
Authenticator.isLoggedIn authentication-service.js:13
RouteConfig.login.resolve.Auth state-service-provider.js:16
invoke angular.js:2902
(anonymous function) angular-ui-router.js:801
forEach angular.js:137
resolve angular-ui-router.js:797
resolveState angular-ui-router.js:813
transitionTo angular-ui-router.js:704
(anonymous function) angular-ui-router.js:615
invoke angular.js:2902
handleIfMatch angular-ui-router.js:433
rule angular-ui-router.js:452
update angular-ui-router.js:487
Scope.$broadcast angular.js:8307
afterLocationChange angular.js:5649
(anonymous function) angular.js:5637
Scope.$eval angular.js:8057
Scope.$digest angular.js:7922
Scope.$apply angular.js:8143
(anonymous function) angular.js:981
invoke angular.js:2895
resumeBootstrapInternal angular.js:979
bootstrap angular.js:993
angularInit angular.js:954
(anonymous function) angular.js:14843
c jquery-1.10.1.min.js:4
p.fireWith jquery-1.10.1.min.js:4
x.extend.ready jquery-1.10.1.min.js:4
q jquery-1.10.1.min.js:4
XMLHttpRequest cannot load http://bac-api/authenticated/. Request header field Content- Type is not allowed by Access-Control-Allow-Headers. manage.bac/:1

So to resolve this error and allow the Content-Type through I go back to my bac-api vhost and I add back Header set Access-Control-Allow-Headers "Content-Type". After restarting browsing to manage.bac on page load here are those results.

POST http://bac-api/authenticated/ 401 (UNAUTHORIZED) bac-api/authenticated/:1

Which this is good because it means I received an {"authenticated": false} response and it sent back a 401 unauthorized. If I continue and try to log in I get these results.

Object {email: "[email protected]", password: "password"} authentication-service.js:36
success authentication-service.js:40
Object {login: false} authentication-service.js:41

This also is working. Here is the angularjs code that is producing this output.

$http.post('http://bac-api/login/', user)
            .success( function( user ) {
                console.log('success');
                console.log(user);                    
            })
            .error( function( error ) {
                console.log('error');
                console.log(error);
            });

Now my thoughts were awesome if its working on port 80 I can switch it over to port 443 now and keep rolling. So I switch the vhosts to port 443 and browse to manage.bac again to log in. On page load before even trying to log in I get these results.

enter image description here

And again when I try to log in and submit the form I get these results also.

Object {email: "[email protected]", password: "password"} authentication-service.js:36
error authentication-service.js:50
         authentication-service.js:51

And also on the Network tab enter image description here

This was just to try and give every body more information in helping me find the reason for this problem.

----- Edit again with more information

Tonight I started trying to test this with curl and see what headers were coming back with an OPTIONS request

Here is the curl request I copied it directly out of the Network tab assuming this was the same OPTIONS request that angularjs is sending over. First Ill put the OPTIONS request from the network tab and then my curl request.

OPTIONS https://bac-api/login/ HTTP/1.1
Access-Control-Request-Method: POST
Origin: https://manage.bac
Referer: https://manage.bac/
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko)    Chrome/29.0.1547.76 Safari/537.36
Access-Control-Request-Headers: accept, origin, x-requested-with, content-type

curl -i -k -X OPTIONS -H "Access-Control-Request-Method: POST" -H "Origin: https://manage.bac" -H "Referer: https://manage.bac/" -H "Access-Control-Request-Headers: accept, origin, content-type" https://bac-api/login/

Here is the response from curl

HTTP/1.1 200 OK
Date: Tue, 01 Oct 2013 02:51:17 GMT
Server: Apache/2.2.22 (Ubuntu)
Allow: POST, OPTIONS
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: POST, OPTIONS
Access-Control-Max-Age: 21600
Content-Length: 0
Access-Control-Allow-Origin: *
Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept
Access-Control-Allow-Methods: POST, GET, OPTIONS
Vary: Accept-Encoding
Content-Type: text/html; charset=utf-8 

This seems to indicate that the server is configured correctly and responding to OPTIONS requests with the correct headers. But as I have posted in the pictures above when I run this in the browser still on the Network tab it is getting cancelled and nothing comes back in the error response.

like image 713
lumberjacked Avatar asked Sep 30 '13 04:09

lumberjacked


1 Answers

I was able to solve this problem and it was the stupidest thing. I will dig up the other stackoverflow posts that helped lead me to the solution. But basically during testing I generated self signed certificates to use locally. Apparently both firefox and chrome will automatically cancel your requests if it does not trust the certificates when doing cross domain posts. I had to go into both chrome and firefox to add the certificates and mark them as trusted before they stopped cancelling the request. Now everything works perfectly.

Also in this investigation I found out that you do not need to add into Flask the requests helper to add headers to your responses and in your Apache configuration. If you put them in both it will add headers both times. In my configuration I chose to add it to my Apache configuration because it made my python code alot cleaner.

like image 165
lumberjacked Avatar answered Oct 01 '22 01:10

lumberjacked