I'm using Django 3.1 with Django's auth application. I have the following middleware defined
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'corsheaders.middleware.CorsMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'directory.middleware.extend_token_response.ExtendTokenResponse'
]
I would like to use Django auth's reset password functionality so I added this in my urls.py view
path('reset_password', views.ResetPasswordView.as_view(), name='password_reset'),
and in my views.py file I have defined
from django.views.decorators.csrf import csrf_exempt
...
class ResetPasswordView(SuccessMessageMixin, PasswordResetView):
template_name = 'users/password_reset.html'
email_template_name = 'users/password_reset_email.html'
subject_template_name = 'users/password_reset_subject'
success_message = "We've emailed you instructions for setting your password, " \
"if an account exists with the email you entered. You should receive them shortly." \
" If you don't receive an email, " \
"please make sure you've entered the address you registered with, and check your spam folder."
success_url = reverse_lazy('users-home')
@csrf_exempt
def post(self, request, *args, **kwargs):
email = request.data.get('email')
try:
if User.objects.get(email=email).active:
print("email: %s " % email)
return super(ResetPasswordView, self).post(request, *args, **kwargs)
except:
# this for if the email is not in the db of the system
return super(ResetPasswordView, self).post(request, *args, **kwargs)
However, the CSRF check doesn't seem to be getting checked for, because when I submit a request like the below
curl 'http://127.0.0.1:8000/reset_password' \
-H 'Accept: */*' \
-H 'Accept-Language: en-US,en;q=0.9' \
-H 'Connection: keep-alive' \
-H 'Content-Type: application/json' \
-H 'Origin: http://localhost:3000' \
-H 'Referer: http://localhost:3000/' \
-H 'Sec-Fetch-Dest: empty' \
-H 'Sec-Fetch-Mode: cors' \
-H 'Sec-Fetch-Site: cross-site' \
-H 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36' \
-H 'sec-ch-ua: "Not?A_Brand";v="8", "Chromium";v="108", "Google Chrome";v="108"' \
-H 'sec-ch-ua-mobile: ?0' \
-H 'sec-ch-ua-platform: "macOS"' \
--data-raw '{"username":"abc@dev.com"}' \
--compressed
I continue to get 403 responses with this message
<p>You are seeing this message because this site requires a CSRF cookie when submitting forms. This cookie is required for security reasons, to ensure that your browser is not being hijacked by third parties.</p>
<p>If you have configured your browser to disable cookies, please re-enable them, at least for this site, or for “same-origin” requests.</p>
django.contrib.auth.views.PasswordResetView
decorates its dispatch
method with csrf_protect
, where csrf_protect = decorator_from_middleware(CsrfViewMiddleware)
.
With your csrf_exempt
decorator on post
and the CsrfViewMiddleware
from MIDDLEWARE
, we have something like CsrfViewMiddleware(csrf_protect(dispatch(csrf_exempt(post))))
.
We can trick csrf_protect
by setting request.csrf_processing_done = True
:
class ResetPasswordView(SuccessMessageMixin, PasswordResetView):
...
@method_decorator(csrf_exempt)
def dispatch(self, request, *args, **kwargs):
request.csrf_processing_done = True
return super().dispatch(request, *args, **kwargs)
# @csrf_exempt # Does nothing
def post(self, request, *args, **kwargs):
...
Alternatively, you can restore PasswordResetView.dispatch
to non-csrf_protect
function:
PasswordResetView.dispatch = csrf_exempt(PasswordResetView.dispatch.__wrapped__)