How to create a POST request (including CSRF token) using Django and AngularJS
Asked Answered
S

6

24

I'm trying to create a POST request using angular.js to this Django view.

class PostJSON4SlickGrid(View):
    """
    REST POST Interface for SlickGrid to update workpackages
    """

    def post(self, request, root_id, wp_id, **kwargs):
        print "in PostJSON4SlickGrid"
        print request.POST
        return HttpResponse(status=200)

Therefore I created this resource.

myModule.factory('gridData', function($resource) {
    //define resource class
    var root = {{ root.pk }};
    return $resource('{% url getJSON4SlickGrid root.pk %}:wpID/', {wpID:'@id'},{
            get: {method:'GET', params:{}, isArray:true},
            update:{method:'POST'}
    });
});

Calling the get method in a controller works fine. The url gets translated to http://127.0.0.1:8000/pm/rest/tree/1/.

function gridController($scope, gridData){
    gridData.get(function(result) {
        console.log(result);
        $scope.treeData = result;
        //broadcast that asynchronous xhr call finished
        $scope.$broadcast('mySignal', {fake: 'Hello!'});  
    });
}

While I m facing issues executing the update/POST method.

item.$update();

The URL gets translated to http://127.0.0.1:8000/pm/rest/tree/1/345, which is missing a trailing slash. This can be easily circumvented when not using a trailing slash in your URL definition.

url(r'^rest/tree/(?P<root_id>\d+)/(?P<wp_id>\d+)$', PostJSON4SlickGrid.as_view(), name='postJSON4SlickGrid'),

instead of

url(r'^rest/tree/(?P<root_id>\d+)/(?P<wp_id>\d+)/$', PostJSON4SlickGrid.as_view(), name='postJSON4SlickGrid'),

Using the workaround without the trailing slash I get now a 403 (Forbidden) status code, which is probably due to that I do not pass a CSRF token in the POST request. Therefore my question boils down to how I can pass the CSRF token into the POST request created by angular?

I know about this approach to pass the csrf token via the headers, but I m looking for a possibility to add the token to the body of the post request, as suggested here. Is it possible in angular to add data to the post request body?

As additional readings one can look at these discussions regarding resources, removed trailing slashes, and the limitations resources currently have: disc1 and disc2. In one of the discussions one of the authors recommended to currently not use resources, but use this approach instead.

Striped answered 10/10, 2012 at 15:55 Comment(0)
C
7

Can't you make a call like this:

$http({
    method: 'POST',
    url: url,
    data: xsrf,
    headers: {'Content-Type': 'application/x-www-form-urlencoded'}
})

The data can be whatever you wish to pass and then just append &{{csrf_token}} to that.

In your resource params:{}, try adding csrfmiddlewaretoken:{{csrf_token}} inside the params

Edit:

You can pass data to the request body as

item.$update({csrfmiddlewaretoken:{{csrf_token}}})

and to headers as

var csrf = '{{ csrf_token }}'; 
update:{method:'POST', headers: {'X-CSRFToken' : csrf }} 

It is an undocumented issue

Compute answered 10/10, 2012 at 17:50 Comment(7)
Thanks. I would prefer to use the resource functionality as it is working in general. I m just missing the possibiliy / syntax to add the CSFR token.Striped
Edited a bit, I think the csrf should go inside your params withing the resource call.Compute
No, unfortunately no luck. I guess Django only recognizes the token when transferred via the body of the POST request. When adding it to the params I get a 403 permission denied, using this angular generated url: POST /pm/rest/tree/1/384?csrfmiddlewaretoken=FKV6x4YUurDEPerFhcGRHK1qVJwzXn HTTP/1.1" 403Striped
Yes it has to be the body, Didn't realize params referred to url params, edited my answer to show how to pass data to request body.Compute
Great, I also just discovered that you can easily set the headers as such: var csrf = '{{ csrf_token }}'; update:{method:'POST', headers: {'X-CSRFToken' : csrf }} . Do you mind to include this solution also in your answer for further reference?Striped
Please note: The approach with using headers is working fine. But if you use item.$update({csrfmiddlewaretoken:{{csrf_token}}}) the token also gets appended to the URL, not as intended to the body of the request.Striped
Docs say 'Calling these methods invoke an ng.$http with the specified http method, destination and parameters.' So if I really wanted to pass it in the body would use $http.post('/someUrl', data).success(successCallback); instead of $resource. Can't seem to find any solution otherwise, other than the headers :(Compute
V
40

I know this is more than 1 year old, but if someone stumbles upon the same issue, angular JS already has a CSRF cookie fetching mechanism (versions of AngularJS starting at 1.1.5), and you just have to tell angular what is the name of the cookie that django uses, and also the HTTP header that it should use to communicate with the server.

Use module configuration for that:

var app = angular.module('yourApp');
app.config(['$httpProvider', function($httpProvider) {
    $httpProvider.defaults.xsrfCookieName = 'csrftoken';
    $httpProvider.defaults.xsrfHeaderName = 'X-CSRFToken';
}]);

Now every request will have the correct django CSRF token. In my opinion this is much more correct than manually placing the token on every request, because it uses built-in systems from both frameworks (django and angularJS).

Vulva answered 10/12, 2013 at 10:48 Comment(4)
And you also need to make sure you wrap your view with ensure_csrf_cookie decorator: docs.djangoproject.com/en/dev/ref/contrib/csrf/…Erfurt
Only if for some reason your view is not sending the CSRF token after you activated the CSRF middleware.Vulva
Thank you very much., i was banging my head from 20 hours.Tops
What happens if your request is not to your backend? Will it still add the cookie to it? Isn't that a security issue? Or does it only do this if you pass relative URLs to $http?Eleen
C
7

Can't you make a call like this:

$http({
    method: 'POST',
    url: url,
    data: xsrf,
    headers: {'Content-Type': 'application/x-www-form-urlencoded'}
})

The data can be whatever you wish to pass and then just append &{{csrf_token}} to that.

In your resource params:{}, try adding csrfmiddlewaretoken:{{csrf_token}} inside the params

Edit:

You can pass data to the request body as

item.$update({csrfmiddlewaretoken:{{csrf_token}}})

and to headers as

var csrf = '{{ csrf_token }}'; 
update:{method:'POST', headers: {'X-CSRFToken' : csrf }} 

It is an undocumented issue

Compute answered 10/10, 2012 at 17:50 Comment(7)
Thanks. I would prefer to use the resource functionality as it is working in general. I m just missing the possibiliy / syntax to add the CSFR token.Striped
Edited a bit, I think the csrf should go inside your params withing the resource call.Compute
No, unfortunately no luck. I guess Django only recognizes the token when transferred via the body of the POST request. When adding it to the params I get a 403 permission denied, using this angular generated url: POST /pm/rest/tree/1/384?csrfmiddlewaretoken=FKV6x4YUurDEPerFhcGRHK1qVJwzXn HTTP/1.1" 403Striped
Yes it has to be the body, Didn't realize params referred to url params, edited my answer to show how to pass data to request body.Compute
Great, I also just discovered that you can easily set the headers as such: var csrf = '{{ csrf_token }}'; update:{method:'POST', headers: {'X-CSRFToken' : csrf }} . Do you mind to include this solution also in your answer for further reference?Striped
Please note: The approach with using headers is working fine. But if you use item.$update({csrfmiddlewaretoken:{{csrf_token}}}) the token also gets appended to the URL, not as intended to the body of the request.Striped
Docs say 'Calling these methods invoke an ng.$http with the specified http method, destination and parameters.' So if I really wanted to pass it in the body would use $http.post('/someUrl', data).success(successCallback); instead of $resource. Can't seem to find any solution otherwise, other than the headers :(Compute
F
1

In recent angularjs version giving solution is not working . So i tried the following

  • First add django tag {% csrf_token %} in the markup.

  • Add a $http inspector in your app config file

angular.module('myApp').config(function ( $httpProvider) {
   $httpProvider.interceptors.push('myHttpRequestInterceptor'); 
});
  • Then define that myHttpRequestInterceptor

angular.module("myApp").factory('myHttpRequestInterceptor', function ( ) {

   return {
             config.headers = { 
              'X-CSRFToken': $('input[name=csrfmiddlewaretoken]').val() } 
             } 
   return config; 
  }}; 
});

it'll add the X-CSRFToken in all angular request

And lastly you need to add the Django middleware " django.middleware.csrf.CsrfViewMiddleware'" It'll solve the CSRF issue

Flotation answered 19/10, 2014 at 3:52 Comment(0)
M
0
var app = angular.module('angularFoo', ....

app.config(["$httpProvider", function(provider) {
  provider.defaults.headers.common['X-CSRFToken'] = '<<csrftoken value from template or cookie>>';
}])
Menopause answered 1/8, 2013 at 21:46 Comment(0)
H
0

I use this:

In Django view:

@csrf_protect
def index(request):
    #Set cstf-token cookie for rendered template
    return render_to_response('index.html', RequestContext(request))

In App.js:

(function(A) {
    "use strict";
    A.module('DesktopApplication', 'ngCookies' ]).config(function($interpolateProvider, $resourceProvider) {
        //I use {$ and $} as Angular directives
        $interpolateProvider.startSymbol('{$');
        $interpolateProvider.endSymbol('$}');
        //Without this Django not processed urls without trailing slash
        $resourceProvider.defaults.stripTrailingSlashes = false; 
    }).run(function($http, $cookies) {
        //Set csrf-kookie for every request
        $http.defaults.headers.post['X-CSRFToken'] = $cookies.csrftoken;
        $http.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded';
    });
}(this.angular));

For sending correct request you must convert object to param-form:

$http.post('/items/add/', $.param({name: 'Foo'}));//Here $ is jQuery
Hedi answered 11/12, 2014 at 17:53 Comment(0)
T
0

I have checked in Angular 15 and Django 4.0 now can fetch csrf-token. Here is solution (is working):

  1. In services file add function
authorizeUser(username: string, password: string, csrfmiddlewaretoken: string): Observable<any>{
  const url = 'http://localhost:8000/admin/login/?next=/admin/'
  const encodedBody = new URLSearchParams();
  encodedBody.set('username', username);
  encodedBody.set('password', password);
  encodedBody.set('csrfmiddlewaretoken', csrfmiddlewaretoken)

  return this.http.post(url, encodedBody);

}
  1. Add component AdminComponent
export class AdminComponent implements OnInit, HttpInterceptor {
  xsrf: string   = <string>this.cookieExtractor.getToken()

  form: FormGroup = new FormGroup({
      username: new FormControl('', Validators.required),
      password: new FormControl('', Validators.required)
    }
  );
  submitted = false
  1. Add in previous component this important function
 intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {

  request = request.clone({
    headers:  new HttpHeaders({ 'Content-Type': 'application/x-www-form-urlencoded', 'Cookie': this.xsrf }),
    withCredentials: true
  });

  return next.handle(request);
}

So the main problem which I have faced with writing application on both frontend and backend service that content-type must be application/x-www-form-urlencoded and not application/json (you can check via Postman sending request in Django-admin area). Hope that this solution will help you!

Townley answered 29/9, 2023 at 12:42 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.