19

In Angular1 the problem can be solved by configuring $http-provider. Like:

app.config(function($httpProvider) {
  $httpProvider.defaults.xsrfCookieName = 'csrftoken';
  $httpProvider.defaults.xsrfHeaderName = 'X-CSRFToken';
});

What is a good practice to do the same in Angular2?

In Angular2 to work with http requests we need to use class Http. Of course that's not a good practice to add CSRF-line to each call of post-function.

I guess in Angular2 I should create own class that inherits Angular2's Http class and redefine the post-function. Is it the right approach or is there a more elegant method?

Viktar K
  • 1,409
  • 2
  • 16
  • 22
  • yes we can one global service and you can write anything whatever you want throughout your whole app. then provide that global service into the bootstrap file.i think this gives you a hint – Pardeep Jain Jan 23 '16 at 03:06
  • Now that version 2.0 has been officially released the previous answers don't seem to work any longer. Please see my answer below on how to use CookieXSRFStrategy. – David Sep 20 '16 at 14:27

7 Answers7

20

Now that Angular 2 is released the following seems to be the correct way of doing this, by using CookieXSRFStrategy.

I've configured my application to have a core module but you can do the same in your main application module instead:

import { ModuleWithProviders, NgModule, Optional, SkipSelf } from '@angular/core';
import { CommonModule }   from '@angular/common';
import { HttpModule, XSRFStrategy, CookieXSRFStrategy } from '@angular/http';

@NgModule({
    imports: [
        CommonModule,
        HttpModule
     ],
    declarations: [ ],
    exports: [ ],
    providers: [
        {
            provide: XSRFStrategy,
            useValue: new CookieXSRFStrategy('csrftoken', 'X-CSRFToken')
        }
    ]
})


export class CoreModule {
}, 
David
  • 1,179
  • 12
  • 15
  • Hi at first run build i got this error: `ERROR in Error encountered resolving symbol values statically. Calling function 'CookieXSRFStrategy', function calls are not supported. Consider replacing t he function or lambda with a reference to an exported function`. But however when hitting save the the cli is building again but now without an error. – Shift 'n Tab Aug 29 '17 at 05:48
  • 1
    @ShiftN'Tab you could replace `new CookieXSRFStrategy('csrftoken', 'X-CSRFToken')` with a function that returns that line. That should solve it. – callback Oct 19 '17 at 14:44
13

Solution for Angular2 is not as easy as for angular1. You need:

  1. Pick out csrftoken cookie value.

  2. Add this value to request headers with name X-CSRFToken.

I offer this snippet:

import {Injectable, provide} from 'angular2/core';
import {BaseRequestOptions, RequestOptions} from 'angular2/http'

@Injectable()
export class ExRequestOptions extends BaseRequestOptions  {
  constructor() {
    super();
    this.headers.append('X-CSRFToken', this.getCookie('csrftoken'));
  }

  getCookie(name) {
    let value = "; " + document.cookie;
    let parts = value.split("; " + name + "=");
    if (parts.length == 2) 
      return parts.pop().split(";").shift();
  }
}

export var app = bootstrap(EnviromentComponent, [
  HTTP_PROVIDERS,
  provide(RequestOptions, {useClass: ExRequestOptions})
]);
Viktar K
  • 1,409
  • 2
  • 16
  • 22
  • Victor, do you need to do something special to get the cookie from the Django server? When I check the cookies the csrftoken doesn't exist. – Bill Noble Apr 01 '16 at 19:22
  • Yes, either your view function must be decorated with @csrf_protect or CsrfViewMiddleware class must be added to MIDDLEWARE_CLASSES in the settings file. Details here https://docs.djangoproject.com/en/1.9/ref/csrf/#module-django.views.decorators.csrf – Viktar K Apr 02 '16 at 20:46
  • for java spring developers X-CSRFToken would be X-CSRF , this is some resources https://spring.io/guides/tutorials/spring-security-and-angular-js/ – AndroidLover Jul 22 '16 at 22:49
  • I don't think this works anymore now the version 2.0 release is out. Please see my alternative answer for a solution which works with 2.0. – David Sep 20 '16 at 14:25
  • Why do we need to get the cookie ourself, when the documentation says it will work out of box using httpclient (problem is, it is not working for me) – netuser Sep 04 '17 at 16:29
10

Victor K's answer is perfectly valid however as of angular 2.0.0-rc.2, a preferred approach would be to use CookieXSRFStrategy as below,

bootstrap(AngularApp, [
  HTTP_PROVIDERS,
  provide(XSRFStrategy, {useValue: new CookieXSRFStrategy('csrftoken', 'X-CSRFToken')})
]);
rsingh25
  • 356
  • 3
  • 9
  • 3
    since provide has been deprecated, I guess the new way should be to replace the provide line with `{provide:XSRFStrategy, useValue: new CookieXSRFStrategy('csrftoken', 'X-CSRFToken')}` – maxbellec Aug 08 '16 at 08:48
  • This solution doesn't work in the release version of Angular 2. Please see my answer below. – David Nov 03 '16 at 09:20
4

For later versions of angular you cannot call functions in decorators. You have to use a factory provider:

export function xsrfFactory() {
  return new CookieXSRFStrategy('_csrf', 'XSRF-TOKEN');
}

And then use the factory:

  providers: [
    {
      provide: XSRFStrategy,
      useFactory : xsrfFactory
  }],

Otherwise the compiler will tell you off. What I have also seen is that ng build --watch will not report this error until you kick it off again.

PeterS
  • 2,818
  • 23
  • 36
3

I struggled with this for a few days. The advice in this article is good, but as of August, 2017 is deprecated (https://github.com/angular/angular/pull/18906). The angular2 recommended approach is simple, but has a caveat.

The recommend approach is to use HttpClientXsrfModule and to configure it to recognize django's default csrf protection. According to the django docs, django will send the cookie csrftoken and expect the client to return the header X-CSRFToken. In angular2, add the following to your app.module.ts

import { HttpClientModule, HttpClientXsrfModule } from '@angular/common/http';

@NgModule({
  imports: [
    HttpClientModule,
    HttpClientXsrfModule.withOptions({
      cookieName: 'csrftoken',
      headerName: 'X-CSRFToken',
    })
  ], ...

The caveat is that angular2's XSRF Protection only applies to mutating requests:

By default, an interceptor sends this cookie [header] on all mutating requests (POST, etc.) to relative URLs but not on GET/HEAD requests or on requests with an absolute URL.

If you need to support an API that performs mutation on GET/HEAD, you will need to create your own custom interceptor. You can find an example and a discussion of the issue here.

user590028
  • 11,364
  • 3
  • 40
  • 57
2

Victor K had the solution, I'll just add this comment here as to what I did:

I created the component "ExRequestOptions" as Victor K said, but I also added a method "appendHeaders" to that component:

appendHeaders(headername: string, headervalue: string) {
    this.headers.append(headername, headervalue);
}

Then I had this in my main.ts:

import {bootstrap}    from 'angular2/platform/browser'
import {AppComponent} from './app.component'
import {HTTP_PROVIDERS, RequestOptions} from 'angular2/http';
import 'rxjs/Rx';
import {ExRequestOptions} from './transportBoxes/exRequestOptions';
import {provide} from 'angular2/core';

bootstrap(AppComponent,[ HTTP_PROVIDERS,  
    provide(RequestOptions, {useClass: ExRequestOptions})]);

I'm not sure the bootstrapping had any effect, so i also did this where I would post data:

    let options = new ExRequestOptions();
    options.appendHeaders('Content-Type', 'application/json');
    return this.http.post('.....URL', JSON.stringify(registration),
         options)
OddBeck
  • 835
  • 8
  • 19
1

Currently, I solve anything with custom headers using a wrapper service around the Http Service. You can add whatever header manually and inject additional services for storing/retrieving values. This strategy also works for JWTs, for example. Have a look at the code below, I hope it helps.

import {Injectable} from '@angular/core';
import {Http, Headers, RequestOptions} from '@angular/http';

@Injectable()
export class HttpService {
  constructor(private http: Http) {
  }

  private get xsrfToken() {
    // todo: some logic to retrieve the cookie here. we're in a service, so you can inject anything you'd like for this
    return '';
  }

  get(url) {
    return this.http.get(url, this.getRequestOptions())
      .map(result => result.json())
      .catch(error => error.json());
  }

  post(url, payload) {
    return this.http.post(url, payload, this.getRequestOptions())
      .map(result => result.json())
      .catch(error => error.json());
  }

  private getRequestOptions() {
    const headers = new Headers({'Content-Type': 'application/json', 'X-XSRF-TOKEN': this.xsrfToken});
    return new RequestOptions({headers: headers});
  }
}
jberndsen
  • 331
  • 2
  • 4