How to register users in Django REST framework?
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)
chaim
Updated on July 08, 2022Comments
-
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 over 10 yearswanted to point out that the reason Cahlan is using init_data instead of data is because
UserSerializer
doesn't read/write passwords. after callingis_valid()
, it's fine if someone wants to useserialized.data['email']
andserialized.data['username']
but password will only be available inserialized.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 over 9 yearsJust curious wouldn't this solution be insecure ? this means that any body with knowledge of this endpoint and keep registering users ?
-
yossi about 9 years@DjangoRocks you are right, but you can use throttling
-
vabada over 8 yearsSorry 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 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-nativeset_password
method to generate a hash for the password. -
blueFast over 8 yearsHow are you handling the password here?
-
Bernard 'Beta Berlin' Parah almost 8 yearswill I have to set a different url for the registerations?
-
Marcel Chastain over 7 yearsDANGER If I'm not mistaken, this code allows submission of
is_superuser
andis_staff
values. Allowed fields should be specified explicitly IMO, as shown in the other examples. -
Purrell over 7 years@yossi The solution is to use CAPTCHA. Throttling doesn't completely address the issue.
-
Hardik Gajjar about 7 yearsis there any way to insert username as email in serialized data?
-
SeedyROM almost 7 yearsThis is the fastest and most up to date way to do this.
-
timi95 over 5 yearsHere'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 over 4 yearsWhy do you use user.set_password instead of setting the password keyword parameter in UserModel.objects.create()?
-
personjerry over 4 yearsAh nevermind I see you didn't use create_user which handles password hashing
-
Oleg Belousov over 4 yearsI 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 almost 4 yearsGood 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 over 3 yearshow can one add extra fields to the registration?
-
ezennnn about 3 yearsyou can use
extra_kwargs = {'password': {'write_only': True}}
instead ofwrite_only_fields
for django versions greater than 3.0 -
uyhaW almost 3 yearsHi @Dunaevsky Maxim, I open a question in stackoverflow.com/questions/68171987/… would you like to take a look ?
-
golkarm over 2 yearsyou couldn't save non hashed password