3
votes

I am using Django with React, I am implementing a method to reset user password when users forget their password. My basic idea is to:

1) Users give their email address

2) Send an email to their email adress with the link to reset their password(with SendGrid api)

3) Users type in new password to reset their password

below is my serializers, views, urls and React code

//views.py
class PasswordResetConfirmSerializer(serializers.Serializer):
    new_password1 = serializers.CharField(max_length=128)
    new_password2 = serializers.CharField(max_length=128)
    uid = serializers.CharField()
    token = serializers.CharField()

    set_password_form_class = SetPasswordForm
    def custom_validation(self, attrs):
        pass
    def validate(self, attrs):
        self._errors = {}
        try:
            self.user = UserModel._default_manager.get(pk=attrs['uid'])
        except (TypeError, ValueError, OverflowError, UserModel.DoesNotExist):
            raise ValidationError({'uid': ['Invalid value']})
        self.custom_validation(attrs)
        self.set_password_form = self.set_password_form_class(
            user=self.user, data=attrs
        )
        if not self.set_password_form.is_valid():
            raise serializers.ValidationError(self.set_password_form.errors)
        return attrs
    def save(self):
        return self.set_password_form.save()

// serializers.py
class PasswordResetConfirmView(GenericAPIView):
    serializer_class = PasswordResetConfirmSerializer
    permission_classes = (AllowAny,)

    @sensitive_post_parameters_m
    def dispatch(self, *args, **kwargs):
        return super(PasswordResetConfirmView, self).dispatch(*args, **kwargs)

    def post(self, request, *args, **kwargs):
        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        serializer.save()
        return Response(
            {"detail": ("Password has been reset with the new password.")}
       )

//urls.py
path('api/passwordreset/confirm/',views.PasswordResetConfirmView.as_view(), name = 'password_reset_confirm')

// React 
    const config = {
    headers: {
      "Content-Type": "application/json"
      }
    };

    const body = JSON.stringify({ 
        new_password1: this.state.new_password,
        new_password2: this.state.confirm_password,
        uid: this.state.fetched_data.pk,
        token: this.state.fetched_data.token
    })

    axios.post(API_URL + 'users/api/passwordreset/confirm/', body, config)

My reset password function itself works fine. But the main problem I am having here is that the reset password API requires 'uid' and 'token'. In order to get these two values, users have to be either logged in, which makes no sense since they forget their password, or to call an api to get the 'uid' and the 'token'. I tried the below method to get those two values:

// views.py
class CustomObtainAuthToken(ObtainAuthToken):
    def post(self, request, *args, **kwargs):
        response = super(CustomObtainAuthToken, self).post(request, *args, **kwargs)
        token = Token.objects.get(key=response.data['token'])
        return Response({'token': token.key, 'id': token.user_id})

// urls.py
path('api/authenticate/', CustomObtainAuthToken.as_view())

// React 
const body = { 
    password: 'mike',
    username: 'Abcd1234'
  }

  await axios.post(API_URL + 'users/api/authenticate/', body)

The function does return the corret 'uid' and 'token', but the problem is that the only thing I am getting from the user is the email address. There is no way for me to also get the password and username to call this API. So I am not really sure how to do this.

Can someone show me the correct way to make this work? Thanks a lot.

1

1 Answers

3
votes

First of all, you can get the uid from the user's email who requested the password reset flow. And secondly this token is not the same token of your authenticated user. This token can be generated with django's default_token_generator.

Here is the usual flow:

  1. User gives email
  2. Frontend hit's an API (i.e. /api/password_reset/) with that email
  3. a) API determines which user's email it is and thus gets an uid
    b) generates a token for that user
    c) sends an email with an url. The url must contain the uid and token as part of it (for example, https://example.com/password-reset-confirm/<uid>/<token>)
  4. This link is clicked by user, it shows a page in frontend, asking for a new password (notice here: this url contains an uid and a token that will be used later)
  5. User gives a new password
  6. Frontend makes API call (i.e. /api/password_reset_confirm/) with the newly given password, along with the uid and token (which were part of the url)
  7. API receives uid, token and new password
    a) it checks if the uid and token are valid (i.e. the token matches the one it previously generated for the same user of that uid)
    b) it sets the new password of the user of that uid
    c) optionally it may log that user out of the system

Since you are using DRF, take a look at the implementation of an authentication library named Djoser. It can be a good learning experience.