How to register users in Django REST framework?

96,017

Solution 1

I went ahead and made my own custom view for handling registration since my serializer doesn't expect to show/retrieve the password. I made the url different from the /users resource.

My url conf:

url(r'^users/register', 'myapp.views.create_auth'),

My view:

@api_view(['POST'])
def create_auth(request):
    serialized = UserSerializer(data=request.DATA)
    if serialized.is_valid():
        User.objects.create_user(
            serialized.init_data['email'],
            serialized.init_data['username'],
            serialized.init_data['password']
        )
        return Response(serialized.data, status=status.HTTP_201_CREATED)
    else:
        return Response(serialized._errors, status=status.HTTP_400_BAD_REQUEST)

I may be wrong, but it doesn't seem like you'll need to limit permissions on this view since you'd want unauthenticated requests ...

Solution 2

Django REST Framework 3 allow override create method in serializers:

from rest_framework import serializers
from django.contrib.auth import get_user_model # If used custom user model

UserModel = get_user_model()


class UserSerializer(serializers.ModelSerializer):

    password = serializers.CharField(write_only=True)

    def create(self, validated_data):

        user = UserModel.objects.create_user(
            username=validated_data['username'],
            password=validated_data['password'],
        )

        return user

    class Meta:
        model = UserModel
        # Tuple of serialized model fields (see link [2])
        fields = ( "id", "username", "password", )

Serialized fields for classes inherited from ModelSerializer must be declared patently in Meta for Django Rest Framework v3.5 and newest.

File api.py:

from rest_framework import permissions
from rest_framework.generics import CreateAPIView
from django.contrib.auth import get_user_model # If used custom user model

from .serializers import UserSerializer


class CreateUserView(CreateAPIView):

    model = get_user_model()
    permission_classes = [
        permissions.AllowAny # Or anon users can't register
    ]
    serializer_class = UserSerializer

Solution 3

The simplest solution, working in DRF 3.x:

class UserSerializer(serializers.ModelSerializer):
    class Meta:
        model = User
        fields = ('id', 'username', 'password', 'email', 'first_name', 'last_name')
        write_only_fields = ('password',)
        read_only_fields = ('id',)

    def create(self, validated_data):
        user = User.objects.create(
            username=validated_data['username'],
            email=validated_data['email'],
            first_name=validated_data['first_name'],
            last_name=validated_data['last_name']
        )

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

        return user

No need for other changes, just make sure that unauthenticated users have the permission to create a new user object.

write_only_fields will make sure passwords (actually: their hash we store) are not displayed, while the overwritten create method ensures that the password is not stored in clear text, but as a hash.

Solution 4

I typically treat the User view just like any other API endpoint that required authorization, except I just override the view class's permission set with my own for POST (aka create). I typically use this pattern:

from django.contrib.auth import get_user_model
from rest_framework import viewsets
from rest_framework.permissions import AllowAny


class UserViewSet(viewsets.ModelViewSet):
    queryset = get_user_model().objects
    serializer_class = UserSerializer

    def get_permissions(self):
        if self.request.method == 'POST':
            self.permission_classes = (AllowAny,)

        return super(UserViewSet, self).get_permissions()

For good measure, here is the serializer I typically use with it:

class UserSerializer(serializers.ModelSerializer):

    class Meta:
        model = get_user_model()
        fields = (
            'id',
            'username',
            'password',
            'email',
            ...,
        )
        extra_kwargs = {
            'password': {'write_only': True},
        }

    def create(self, validated_data):
        user = get_user_model().objects.create_user(**validated_data)
        return user

    def update(self, instance, validated_data):
        if 'password' in validated_data:
            password = validated_data.pop('password')
            instance.set_password(password)
        return super(UserSerializer, self).update(instance, validated_data)

djangorestframework 3.3.x / Django 1.8.x

Solution 5

I updated Cahlan's answer to support custom user models from Django 1.5 and return the user's ID in the response.

from django.contrib.auth import get_user_model

from rest_framework import status, serializers
from rest_framework.decorators import api_view
from rest_framework.response import Response

class UserSerializer(serializers.ModelSerializer):
    class Meta:
        model = get_user_model()

@api_view(['POST'])
def register(request):
    VALID_USER_FIELDS = [f.name for f in get_user_model()._meta.fields]
    DEFAULTS = {
        # you can define any defaults that you would like for the user, here
    }
    serialized = UserSerializer(data=request.DATA)
    if serialized.is_valid():
        user_data = {field: data for (field, data) in request.DATA.items() if field in VALID_USER_FIELDS}
        user_data.update(DEFAULTS)
        user = get_user_model().objects.create_user(
            **user_data
        )
        return Response(UserSerializer(instance=user).data, status=status.HTTP_201_CREATED)
    else:
        return Response(serialized._errors, status=status.HTTP_400_BAD_REQUEST)
Share:
96,017
chaim
Author by

chaim

Updated on July 08, 2022

Comments

  • chaim
    chaim almost 2 years

    I'm coding a REST API with Django REST framework. The API will be the backend of a social mobile app. After following the tutorial, I can serialise all my models and I am able to create new resources and update them.

    I'm using AuthToken for authentication.

    My question is:

    Once I have the /users resource, I want the app user to be able to register. So, is it better to have a separate resource like /register or allow anonymous users to POST to /users a new resource?

    Also, some guidance about permissions would be great.

  • Admin
    Admin over 10 years
    wanted to point out that the reason Cahlan is using init_data instead of data is because UserSerializer doesn't read/write passwords. after calling is_valid(), it's fine if someone wants to use serialized.data['email'] and serialized.data['username'] but password will only be available in serialized.init_data['password']. Also the order of email and username params should be switched (at least in Django 1.6). or you can always pass named parameters e.g. User.objects.create_user(email='[email protected]', username='admin', password='admin123')
  • DjangoRocks
    DjangoRocks over 9 years
    Just curious wouldn't this solution be insecure ? this means that any body with knowledge of this endpoint and keep registering users ?
  • yossi
    yossi about 9 years
    @DjangoRocks you are right, but you can use throttling
  • vabada
    vabada over 8 years
    Sorry if I'm wrong, but is it explicitely needed to override the create method? I tried just adding the write_only_fields and read_only_fields and it worked as I expected it to. Any clues?
  • cpury
    cpury over 8 years
    @dabad If you do that, the password will probably get stored in clear text in the database, something you absolutely don't want. The only line the custom create method adds is the Django-native set_password method to generate a hash for the password.
  • blueFast
    blueFast over 8 years
    How are you handling the password here?
  • Bernard 'Beta Berlin' Parah
    Bernard 'Beta Berlin' Parah almost 8 years
    will I have to set a different url for the registerations?
  • Marcel Chastain
    Marcel Chastain over 7 years
    DANGER If I'm not mistaken, this code allows submission of is_superuser and is_staff values. Allowed fields should be specified explicitly IMO, as shown in the other examples.
  • Purrell
    Purrell over 7 years
    @yossi The solution is to use CAPTCHA. Throttling doesn't completely address the issue.
  • Hardik Gajjar
    Hardik Gajjar about 7 years
    is there any way to insert username as email in serialized data?
  • SeedyROM
    SeedyROM almost 7 years
    This is the fastest and most up to date way to do this.
  • timi95
    timi95 over 5 years
    Here's an extra bit for those wanting to keep the password field hidden : class UserSerializer(serializers.HyperlinkedModelSerializer): password = serializers.CharField( write_only=True, style={'input_type': 'password', 'placeholder': 'Password'}, )
  • personjerry
    personjerry over 4 years
    Why do you use user.set_password instead of setting the password keyword parameter in UserModel.objects.create()?
  • personjerry
    personjerry over 4 years
    Ah nevermind I see you didn't use create_user which handles password hashing
  • Oleg Belousov
    Oleg Belousov over 4 years
    I looked at all the answers and it seems that everybody suggest to do Model-saving logic in the serialiser. I reckon this is against the Django MVVM guidelines where the 'controller' logic should be in the view
  • rottweilers_anonymous
    rottweilers_anonymous almost 4 years
    Good point. Worse still, doing it in two steps seems like a security concern. Without it being done in an atomic transaction, if there's an error between where the user is created and the user is saved with a hashed password then the data saved in the DB is in cleartext
  • uber
    uber over 3 years
    how can one add extra fields to the registration?
  • ezennnn
    ezennnn about 3 years
    you can use extra_kwargs = {'password': {'write_only': True}} instead of write_only_fields for django versions greater than 3.0
  • uyhaW
    uyhaW almost 3 years
    Hi @Dunaevsky Maxim, I open a question in stackoverflow.com/questions/68171987/… would you like to take a look ?
  • golkarm
    golkarm over 2 years
    you couldn't save non hashed password