Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ng2 get csrf token from cookie post it as header

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:

  • How and where do i access the x-csrf-token, and how do i add it to my requests?
  • 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. 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

like image 996
VikingCode Avatar asked Apr 12 '17 08:04

VikingCode


2 Answers

Update for angular 5.0+

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{}

For future readers:

Ajax response cookies

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

But my cookie isn't httpOnly

Good for you, but httpOnly only states that your cookie cannot be accessed via document.cookie (see further).

The document.cookie API

The 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.

How do I implement my csrf protection, then ?

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.

Original answer

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.

CookieXSRFStrategy

/**
 * `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.

Billion dollar solution

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.

like image 92
n00dl3 Avatar answered Oct 31 '22 00:10

n00dl3


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()
            ....    
like image 1
MarkJ Avatar answered Oct 31 '22 00:10

MarkJ