After spending 2 full days searching the web and reading docs and tons of open questions of people facing the same problem, i still don't grasp how Angular 2 handles the (x-origin) cookies and how to access them.
The problem: Back-end sends 2 cookies with x-csrf-token & JSESSIONID inside of it. My job is to keep the csrf token in memory (ng2) and send it (only) back as header (not cookie) with every post to the back-end.
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Access-Control-Allow-Origin: http://localhost:4200
Access-Control-Allow-Credentials: true
Access-Control-Expose-Headers: Access-Control-Allow-Origin,Access-Control-Allow-Credentials
Set-Cookie: x-csrf-token=8555257a-396f-43ac-8587-c6d489e76026; Path=/app
Set-Cookie: JSESSIONID=73E38392C60370E38FBAF80143ECE212; Path=/app/; HttpOnly
Expires: Thu, 12 Apr 2018 07:49:02 GMT
Cache-Control: max-age=31536000
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Wed, 12 Apr 2017 07:49:02 GMT
My partial solution: I created a custom RequesstOptions class that extends the BaseRequestOptions. Added some extra headers, and set the 'withCredentials' as true.
export class MyRequestOptions extends BaseRequestOptions {
headers: Headers = new Headers({
'Accept': 'application/json',
'Content-Type': 'application/json',
});
withCredentials = true;
}
In my HttpService i do the post and get like so:
@Injectable()
export class HttpService {
constructor(
protected _http: Http,
protected requestOptions: RequestOptions
) { }
get(url): Observable<any> {
return this._http.get(url, this.requestOptions).map( res => res.json() );
}
post(url: string, object: any): Observable<any> {
return this._http.post(url, object, this.requestOptions).map( res => res.json() );
}
}
and in my app.module i do the magic like so:
providers: [
{ provide: RequestOptions, useClass: DocumentumDefaultRequestOptions },
{ provide: XSRFStrategy, useFactory: xsrfFactory }
],
my xsrfFactory
export function xsrfFactory() {
return new CookieXSRFStrategy('x-csrf-token', 'x-csrf-token');
}
My partial result: At this point angular sends a cookie with every request (GET and POST without discrimination) with the jsessionid and x-csrf-token like so:
POST /app/business-objects/business-objects-type HTTP/1.1
Host: localhost:8040
Connection: keep-alive
Content-Length: 26
Pragma: no-cache
Cache-Control: no-cache
Authorization: Basic ZG1hZG1pbjphZG1pbg==
Origin: http://localhost:4200
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36
Content-Type: application/json
Accept: application/json
Referer: http://localhost:4200/page
Cookie: JSESSIONID=B874C9A170EFC12BEB0EDD4266896F2A; x-csrf-token=0717876e-f402-4a1c-a31a-2d60e48509d3
My billion dollar questions:
CookieXSRFStrategy('x-csrf-token', 'x-csrf-token');
exactly do. I don't like the blackbox feeling / understand the way the docs explained it. Can i access it for data ?Before sending an HTTP request, the CookieXSRFStrategy looks for a cookie called XSRF-TOKEN and sets a header named X-XSRF-TOKEN with the value of that cookie.
It doesn't set the header in my case ... but why ?
Right now i'm sending the cookie to the backend with the sessionid and the csrf token but what is sending it? The CookieXSRFStrategy or 'withCredentials' flag.
Please don't answer with one liner like "document.cookie". Data is useless without its metadata
Http
service beign deprecated in favor of HttpClient
, the CookieXSRFStrategy
class has been deprecated too, now this mission is delegated to the HttpClientXsrfModule
class. If you want to customize the header and cookie names, you just need to import this module like so :
@NgModule({
imports: [
HttpClientModule,
HttpClientXsrfModule.withOptions({
cookieName: 'My-Xsrf-Cookie',
headerName: 'My-Xsrf-Header',
}),
]
})
export class MyModule{}
You cannot access any cookie from any Ajax response, if you check the XHR spec, you will notice that any access to a header matching "Set-Cookie" is forbidden :
A forbidden response-header name is a header name that is a byte-case-insensitive match for one of:
Set-Cookie
Set-Cookie2
httpOnly
Good for you, but httpOnly
only states that your cookie cannot be accessed via document.cookie
(see further).
document.cookie
APIThe only cookies you will be able to access with javascript is document.cookie
but document.cookie
refers to the cookie that has been sent with the document (the page your script is running on) and won't get modified at any time. MDN states clearly it refers to the current document:
Document.cookie
Get and set the cookies associated with the current document. For a general library see this simple cookie framework.
Source : MDN
Any cookie set by an Ajax response does not belong to the current document.
The cookie to header token protection is the way to go. Note that the token you will send is the same during the whole session, and it is not suposed to change according to wild Ajax requests sending cookies.
Web applications that use JavaScript for the majority of their operations may use an anti-CSRF technique that relies on same-origin policy:
On login, the web application sets a cookie containing a random token that remains the same for the whole user session
Set-Cookie: Csrf-token=i8XNjC4b8KVok4uw5RftR38Wgp2BFwql; expires=Thu, 23-Jul-2015 10:25:33 GMT; Max-Age=31449600; Path=/
JavaScript operating on the client side reads its value and copies it into a custom HTTP header sent with each transactional request
X-Csrf-Token: i8XNjC4b8KVok4uw5RftR38Wgp2BFwql
The server validates presence and integrity of the token
Security of this technique is based on the assumption that only JavaScript running within the same origin will be able to read the cookie's value. JavaScript running from a rogue file or email will not be able to read it and copy into the custom header. Even though the csrf-token cookie will be automatically sent with the rogue request, the server will be still expecting a valid X-Csrf-Token header.
Source: Wikipedia : CSRF
With Angular 2+ this mission is fulfilled by the CookieXSRFStrategy
class.
How and where do i access the x-csrf-token, and how do i add it to my requests?
Using CookieXSRFStrategy
seems to be the way to go to add it to your request. For the "how", unfortunately, the answer might be "you can't" (see further).
What does CookieXSRFStrategy('x-csrf-token', 'x-csrf-token'); exactly do. I don't like the blackbox feeling / understand the way the docs explained it.
/** * `XSRFConfiguration` sets up Cross Site Request Forgery (XSRF) protection for the application * using a cookie. See https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF) * for more information on XSRF. * * Applications can configure custom cookie and header names by binding an instance of this class * with different `cookieName` and `headerName` values. See the main HTTP documentation for more * details. * * @experimental */ export class CookieXSRFStrategy implements XSRFStrategy { constructor( private _cookieName: string = 'XSRF-TOKEN', private _headerName: string = 'X-XSRF-TOKEN') {} configureRequest(req: Request): void { const xsrfToken = getDOM().getCookie(this._cookieName); if (xsrfToken) { req.headers.set(this._headerName, xsrfToken); } } }
Source
Basically, it reads the cookie from document.cookie
and modify the Request
headers accordingly.
Right now i'm sending the cookie to the backend with the sessionid and the csrf token but what is sending it? The CookieXSRFStrategy or 'withCredentials' flag.
That's withCredentials
flag, this flag indicates that the XHR should send all the cookies that have been sent (even those previously set by Ajax response, but as cookies, not headers, and there is no way to change this behavior)
It doesn't set the header in my case ... but why ?
The cookie you are talking about is not sent with the document (index.html
) but from another ajax request. The fact is you cannot access cookies set by ajax response (see this answer), because that would be a security issue: a simple ajax get on www.stackoverflow.com
from a random web page would get the stack overflow cookie, and an attacker could steal it easily (if an Access-Control-Allow-Origin: *
header is present on stackoverflow response).
On the other hand, the document.cookie
API can only access to the cookies that are set when the document has been loaded, not any other.
So you should rethink the flow of your client/server communication on the server-side, because the only cookie that you will be able to copy to the headers is the one that has been sent with the document your script is running on (index.html).
it isn't a httpOnly cookie so it should be accessible with js even if it is X origin
httpOnly
makes the cookie unavailable to the document.cookie
API, but as I told you, document.cookie
refers to the cookie that has been sent with the document, not those sent via Ajax responses. You can make thousands of ajax call with a Set-Cookie
Header in the response, document.cookie
will still be the same string, without any modification.
The server should only send one x-csrf-token
cookie containing the token with the document, and you should use that token that will be valid for the whole session for every request using CookieXSRFStrategy
. Why ? because that is how it works.
Angular has built in support for XSRF see here: https://angular.io/guide/http#security-xsrf-protection
"When performing HTTP requests, an interceptor reads a token from a cookie, by default XSRF-TOKEN, and sets it as an HTTP header, X-XSRF-TOKEN"
So if your server sets a cookie named XSRF-TOKEN then it will work automatically! Nothing to do on the client side. If you want to name your cookie/header something else then you can do that as well:
imports: [
HttpClientModule,
HttpClientXsrfModule.withConfig({
cookieName: 'My-Xsrf-Cookie',
headerName: 'My-Xsrf-Header',
}),
]
AND if you are using spring security, it supports the angular naming conventions so you can configure this server side:
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
.and()
.authorizeRequests()
....
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With