3

I will be using Django only as the backend. The front end will be done using React and no django templates. I am using django-rest-framework to create a rest api for my website.

I made a serializer for the user.

class CustomUserSerializer(serializers.ModelSerializer):
    class Meta:
        model = CustomUser
        fields = (
            'id', 'email', 'password', 'username', 'first_name', 'last_name', 'date_of_birth', 'gender', 'mobile_number'
        )
        extra_kwargs = {
            'password': {'write_only': True},
            'id': {'read_only': True}
        }

    def create(self, validated_data):
        user = CustomUser.objects.create(
            email=validated_data['email'],
            username=validated_data['email'],
            first_name=validated_data['first_name'],
            last_name=validated_data['last_name'],
            date_of_birth=validated_data['date_of_birth'],
            gender=validated_data['gender'],
            mobile_number=validated_data['mobile_number']
        )
        user.set_password(validated_data['password'])
        user.save()
        return user

class CustomUserViewSet(viewsets.ModelViewSet):
    queryset = CustomUser.objects.all()
    serializer_class = CustomUserSerializer

In the browser, when I go to /custom/users/ I can view the users. I can also create new users which after successful registration returns back the user. Also it works if I use httpie/curl.

(djangoweb) vagrant@precise32:~$ http --json POST http://55.55.55.5/custom/users/ email="ter23minal2@gmail.com" password="terminal2123" username="t223erm" first_name="te2er" last_name="mi2nal" date_of_birth=1992-12-12 gender=2 mobile_number=66222666666336

It creates and returns the new user object.

So I made a form to register a user which I am not serving from the django server:

<form action="http://55.55.55.5/custom/users/" method="post" id="register-form">
    <input type="text" placeholder="email" name="email"/>
    ...
    ...
    <button id="post">Register</button>
</form>

And ajax to post the form.

// using the javscript Cookies library
var csrftoken = Cookies.get('csrftoken');

function csrfSafeMethod(method) {
    return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
}
$.ajaxSetup({
    beforeSend: function(xhr, settings) {
        if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
            xhr.setRequestHeader("X-CSRFToken", csrftoken);
        }
    }
});

$('#post').click(function(event) {
    event.preventDefault();
    var $form = $('#register-form');
    var data = $form.serialize();        
    $.ajax({
        type: "POST",
        url: "http://55.55.55.5/custom/users/",
        data: JSON.stringify(data),
        sucess: function() { console.log("Success!"); },
        contentType: "application/json; charset=utf-8",
        dataType: "json",
        crossDomain:false,
        beforeSend: function(xhr, settings) {
          xhr.setRequestHeader("X-CSRFToken", csrftoken);
        }
    });
});

Now after if I click the button, the problem starts:

  • Even though I gave event.preventDefault() the page is automatically loaded to the url of the django server (i.e., http://55.55.55.5/custom/users/).
  • I get an error: "detail": "CSRF Failed: CSRF token missing or incorrect."

How can I handle the post to the django server using django-rest-framework? I didn't find any helping material for this problem. Could you please guide me how to? Thank you.

Robin
  • 5,366
  • 17
  • 57
  • 87
  • Can you clarify who is serving the front-end page? Is is a static HTML/JS directly rendered by a server, or is Django rendering it? Assuming it is a static file, then I am not sure it makes sense to even use CSRF, as you should probably be using a token or similar for security. – dkarchmer Jul 23 '16 at 21:09
  • @dkarchmer Yes, it is a static HTML/JS rendered by nodejs. How can I use token if I am not a user yet? I thought tokens were given if a user authenticates. Could you please guide me. Thank you. – Robin Jul 24 '16 at 04:40

1 Answers1

3

You can use csrf_exempt for the registration and login functions. As an example, here how you can create the registration and login APIs. See how my login API returns the token. See http://www.django-rest-framework.org/api-guide/authentication/#tokenauthentication.

I tried to edit my code to replace with your model names, but I did not test it, so you may need to fix any typos I have (or let me know so I can fix them).

class AccountViewSet(viewsets.ModelViewSet):
    queryset = CustomUser.objects.all()
    serializer_class = CustomUserSerializer

    def get_permissions(self):

        if self.request.method in permissions.SAFE_METHODS:
            return (permissions.IsAuthenticated(),)

        if self.request.method == 'POST':
            return (permissions.AllowAny(),)

        return (permissions.IsAuthenticated(), IsAccountOwner(),)

    @csrf_exempt
    def create(self, request):
        '''
        When you create an object using the serializer\'s .save() method, the
        object\'s attributes are set literally. This means that a user registering with
        the password \'password\' will have their password stored as \'password\'. This is bad
        for a couple of reasons: 1) Storing passwords in plain text is a massive security
        issue. 2) Django hashes and salts passwords before comparing them, so the user
        wouldn\'t be able to log in using \'password\' as their password.

        We solve this problem by overriding the .create() method for this viewset and
        using Account.objects.create_user() to create the Account object.
        '''

        serializer = self.serializer_class(data=request.data)

        if serializer.is_valid():
            password = serializer.validated_data['password']
            confirm_password = serializer.validated_data['confirm_password']

            if password and confirm_password and password == confirm_password:

                user = CustomUser.objects.create_user(**serializer.validated_data)

                user.set_password(serializer.validated_data['password'])
                user.save()

                return Response(serializer.validated_data, status=status.HTTP_201_CREATED)

        return Response({'status': 'Bad request',
                         'message': 'Account could not be created with received data.'
                        }, status=status.HTTP_400_BAD_REQUEST)

class APILoginViewSet(APIView):

    @csrf_exempt
    def post(self, request, format=None):
        data = JSONParser().parse(request)
        serializer = LoginCustomSerializer(data=data)

        if serializer.is_valid():
            email = serializer.data.get('email')
            password = serializer.data.get('password')

            if not request.user.is_anonymous():
                return Response('Already Logged-in', status=status.HTTP_403_FORBIDDEN)

            user = authenticate(email=email, password=password)

            if user is not None:
                if user.is_active:
                    login(request, account)

                    serialized = UserSerializer(user)
                    data = serialized.data

                    # Add the token to the return serialization
                    try:
                        token = Token.objects.get(user=user)
                    except:
                        token = Token.objects.create(user=user)

                    data['token'] = token.key

                    return Response(data)
                else:
                    return Response('This account is not Active.', status=status.HTTP_401_UNAUTHORIZED)
            else:
                return Response('Username/password combination invalid.', status=status.HTTP_401_UNAUTHORIZED)

        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

    def get(self, request, format=None):
        data_dic = {"Error":"GET not supported for this command"}
        return Response(data_dic, status=status.HTTP_400_BAD_REQUEST)

You can see a full working example at https://github.com/dkarchmer/django-aws-template (disclaimer, that's my code).

Hope this helps you

dkarchmer
  • 5,434
  • 4
  • 24
  • 37