I'm making a call to the WebApi service, which sets the cookie in the response object.
The call is made from angularjs via $resource
So this is the server code:
CookieHeaderValue cookie = new CookieHeaderValue("Token", "blah") { HttpOnly = true, Expires = DateTime.Now.AddYears(10), Path="/" };
response.Headers.AddCookies(new CookieHeaderValue[] { cookie });
This works, I can see the Set-Cookie header in a response, however the cookie is not being set.
A friend of mine had to set xhrFields' withCredentials to true when he was using jQuery, so I wonder if there's something that needs to be configured in angular as well ?
There could be a number of things going on.
First, since you are on separate domains, you may need to implement CORs (cross origin resource sharing), but it seems that the request is being made successfully. I'm not sure why that works, I would think that browsers would prevent it. In any case here's a jsfiddle that illustrates using CORs with angularjs to make both $http & $resource requests. The trick seems to be to configure the $http service:
$http.defaults.useXDomain = true;
Another thought is that cookies from one domain, can't be accessed by another domain. Here is another question on cookies with angularjs, but the request and server seem to be on the same domain. Here is a discussion on cookie domains, and how they are applied.
If it's possible I would try to get the cookie request/response working on the same domain, and then move the client to another domain.
Related
I'm developing an angular web application that will replace the current website that we have. The current website uses session based authentication. At the moment, I can't access the hosted API with get or post requests.
I'm developing the angular application on my local computer using a python simple server, whereas the api is hosted online.
I would prefer to find a fix that's completely in angular since I can't change the API without help (it was written by my boss a while ago, and is now used in the production version). I don't have a login page so I'm just trying to provide the authentication information in my headers and requests.
My angular application was written independent of django. I just want to access the django backend
So far I'm trying the following to set the headers:
app.config(['$httpProvider', function($httpProvider) {
$httpProvider.defaults.xsrfCookieName = 'csrftoken';
$httpProvider.defaults.xsrfHeaderName = 'X-CSRFToken';
$httpProvider.defaults.headers.common = {'username': btoa('myUsername'), 'password': btoa('myPassword')
};
}]);
And in my service:
app.factory('Test', ['$resource', function($resource) {
return $resource('https://www.phonywebsite.org/en/api/test/')
};
I consistently get 301, 400 and 403 errors. Lately it's been mostly 301 errors and I get no response from the api. I'm using the Allow CORS chrome extension as a temporary fix to try to get to the api without getting a CORS policy error.
My questions
How can I fix the CORS errors without using the chrome extension?
How do I provide my authentication to my django backend that uses session based authentication making sure the csrf cookie its looking for is in the header?
To answer your first question, using the cors extension is a temporary solution and should mostly never be used cause your clients might not use it. To handle CORS, you need to understand how cross site API calls work. In short CORS is a mechanism that allows AJAX requests to circumvent their same origin limits. To handle such situations you need to update your backend and add
CORS_ORIGIN_ALLOW_ALL = True
CORS_ALLOW_CREDENTIALS = True
. Once you add this your settings.py should stop getting CORS issues.
To answer your second question, angular already provides you with support for CSRF so half of your battle is already won. What you need to do is add a patch on your module to start accepting csrf tokens (The name is a bit different in angular). You have already done this and done a good job of it as well:
var app = angular.module('app', ['...']);
app.config(['$httpProvider', function($httpProvider) {
$httpProvider.defaults.xsrfCookieName = 'csrftoken';
$httpProvider.defaults.xsrfHeaderName = 'X-CSRFToken';
}]);
What this would do is make sure that whenever you make a $http call your csrf token is set as well.
As a learning oppurtunity, you could also try using ng-cookies as well. To go further to explain this, whenever you make a call in angular , your request in bundled with cookies as well so you can easily access them in request.COOKIES.
You need to change how you are calling your API as well, something like:
app.factory('APIService', function ($http) {
return $http({url: 'https://www.phonywebsite.org/en/api/test/',
method: 'GET'})
}
You can obviously make modifications to this but I think this shows the $http usage to make you understand the general gist.
You can try to add some more authentication around your application here as well (or replace django auth with your own custom auth), but that is on your use case.
Hope this helps.
I'm writing a small app using angular.
I need to access a couch database. I only have a user in that DB.
Using cURL commands I can request a session cookie to that database and that use that cookie to request a specific document.
When in angular code I use:
$http.post(url, data).then(...);
Where 'data' has username and password, I can see the cookie using chrome (like in this image chrome dev tool output), but in a code, I can't access it.
Can someone help me with this?
You can not access an HttpOnly cookie using script in browser due to security reasons. It can only be accessed on server.
If you need to include cookies in cross domain requests use withCredentials in the request. See $http docs
$http.post returns a response with a property:
headers – {function([headerName])} – Header getter function.
So you should be able to do something like
.then((response) => {
console.log(response.headers('set-cookie'));
});
See the documentation here
Try to use response.headers('set-cookie')
For the crossdomain situation, the server has to send
Access-Control-Expose-Headers: set-cookie header to make the custom headers visible.
https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS#Access-Control-Expose-Headers
I have two web apps, one for the Web UI in AngularJS and one for the REST webservices in Java. Both are deployed on separate domains.
The applications uses cookie for authentication. Whenever user enters a valid username and password, server returns a http only cookie back containing the token and that cookie is passed across all requests. I have enabled CORS on both apps, thats why the session cookie is working properly.
Now, I am trying to add CSRF protection for this. I was trying to use the csrf cookie where in the server will send the csrf cookie(not httponly) as part of REST response and the UI will read the value from the cookie and pass that in a csrf token header for the other REST calls.
The problem with this approach I am facing is that since the server is in different domain, I cannot read the cookie using $cookies in AngularJs. Is there a way to read a value of that cookie?
If not, then can I implement CSRF in some other way?
I also tried to implement the creation of the csrf cookie on the Web UI itself in the browser but the browser does not send the cookie to the webservice as its in different domain.
So, my question is how to implement csrf protection for this kind of situation?
You were on the right track with this:
I also tried to implement the creation of the csrf cookie on the Web UI itself in the browser but the browser does not send the cookie to the webservice as its in different domain.
The CSRF cookie isn't meant to be "sent" to the server, it is meant to be read by the client and then supplied in a custom HTTP request header. Forged GET requests (triggered by HTML tags such as <img src="">) from other domains cannot set custom headers, so this is how you assert that the request is coming from a javascript client on your domain.
Here is how you can implement the idea you were working on, imagine you have api.domain.com and ui.domain.com:
1) User loads the Angular client from ui.domain.com
2) User posts authentication information from Angular client to api.domain.com
2) Sever replies with an HttpOnly authentication cookie, called authCookie, and a custom header e.g. X-Auth-Cookie, where the value of this header is a unique value that is linked to the session that is identified by the authCookie
3) The Angular client reads the X-Auth-Cookie header value and stores that value in a XSRF-TOKEN cookie on its domain, ui.domain.com
So now you have:
XSRF-TOKEN cookie on ui.domain.com
authCookie cookie on api.domain.com
4) User makes a request of a protected resource on api.domain.com. The browser will automatically supply the authCookie value, and Angular will automatically send the X-XSRF-TOKEN header, and will send the value that it reads from the XSRF-TOKEN cookie
5) Your server asserts that the value of X-XSRF-TOKEN is linked to the same session that is identified by the value of the authCookie
I hope this helps! I've also written about token authentication for Angular, Token Based Authentication for Single Page Apps (SPAs) (Disclaimer: I work at at Stormpath)
Angularjs has built-in support for CSRF but unfortunately it doesn't work cross domain, so you have to build your own.
I managed to get it working by first returning a random token in the headers and cookies on the first request. In order to read the header you need to add it to Access-Control-Expose-Headers. This is then added to all posts
$http.get('url').
success(function(data, status, headers) {
$http.defaults.headers.post['X-XSRF-TOKEN'] = headers('XSRF-TOKEN');
});
Then on the server you can compare the cookie value with the value in the header to ensure they are the same.
$http docs : Angular provides a mechanism to counter XSRF. When performing XHR requests, but will not be set for cross-domain requests.
This is a small lib put together might help you https://github.com/pasupulaphani/angular-csrf-cross-domain
I have a angular app that I needed to redirect outside to a non angular html page, so I thought I could just use the $window.location.hrefto redirect the angular app to my external site. This actually works fine, however, I have a nodejs/express backend that checks for auth token before serving up any content(even static content).
This requires a auth token to be sent in the header of the http request. Now the question:
Can/How do you add an auth token to the request that is made by changing the $window.location.href before it is sent off?
When you use $window.location.href the browser is making the HTTP request and not your JavaScript code. Therefore, you cannot add a custom header like Authorization with your token value.
You could add a cookie via JavaScript and put your auth token there. The cookies will automatically be sent from the browser. However, you will want to review the security implications of using a cookie vs. a header. Since both are accessible via JavaScript, there is no additional attack vector there. Unless you remove the cookie after the new page loads, there may be a CSRF exploit available.
This answer is NOT a safe way, as the token is exposed in the URL, which is logged in browser history, access logs, etc. Use a domain cookie instead. I'll leave the answer as it can be an easy way to debug in your local setup.
I am using JWT as authentication on a Laravel PHP backend, and it works by putting ?token=... in the URL. For example, when using AngularJS with satellizer plug-in, I add ?token=' + $auth.getToken() to the URL.
I am using a $resource in my angularJS app. Does it send automatically my cookies? I am doing requests on the same domain.
Browser will always send a cookie along with the request (no matter if it's an XHR request or not) as long as all assumptions are met (same domain, matching path, matching port, same protocol, not expired, etc.).
Since $resource service is just a simple Ajax wrapper your cookies will/should be sent (if everything's in place).
No. But if you want to send cookies, then you can try $cookies service to get the cookie and send with API either in the payload or included in the header.
You can also set the cookie in a default header (with $cookies service injected) so you don't have to specify it in all API calls.
var cookie = $cookies.myCookie; // suppose you already set $cookies.myCookie= 'xxx';
$http.defaults.headers.post.Cookies = cookie;
Note that running different applications on the same domain but on different ports might also be a reason for why cookies are not sent.
Cookies should not be port specific (regarding SOP), but CORS definitely is. Also see Are HTTP cookies port specific?
In my experience no current Browser (FF 47, Chrome 51, IE11) sends cookies for example from localhost:3000 to localhost:8080 in a XHR request.