I'm using Django 2.0.10 with rest framework, rest-auth and allauth, with a React front end. Rest-auth provides login and logout functionality using JWT token authentication, but I can't work out how to allow the user to request a resend of the verification email.
I want a user to be able to log in and press a button saying "Resend confirmation email". If for example they accidentally deleted the email, they need to be able to request another.
I've seen posts suggesting that you can use send_email_confirmation from allauth, but this expects a CSRF token which would be generated by a template. I tried following the docs to excempt from csrf, but it doesn't make any different. I also tried setting authentication_classes = () as suggested here. Here's my code:
settings:
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework.authentication.TokenAuthentication',
],
}
urls.py
from users.views import EmailConfirmation
urlpatterns = [
...
url(r'^/sendconfirmationemail/', EmailConfirmation.as_view(), name='send-email-confirmation')
]
views.py
from rest_framework.views import APIView
from django.views.decorators.csrf import csrf_exempt
from django.utils.decorators import method_decorator
class EmailConfirmation(APIView):
@method_decorator(csrf_exempt)
authentication_classes = ()
def post(self):
send_email_confirmation(user=self.request.user)
When I post to the endpoint '/api/v1/rest-auth/sendconfirmationemail/', I get an error Forbidden:
<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>
Edit: I have also tried to add the CSRF token to my request following tutorials like this one. But I have the same problem. Here's what I've tried:
function getCookie(name) {
var cookieValue = null;
if (document.cookie && document.cookie !== '') {
var cookies = document.cookie.split(';');
for (var i = 0; i < cookies.length; i++) {
var cookie = cookies[i].trim();
//var cookie = jQuery.trim(cookies[i]);
// Does this cookie string begin with the name we want?
if (cookie.substring(0, name.length + 1) === (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
And then I construct my request to use fetch API like this:
curl 'http://localhost:3000/api/v1/rest-auth/sendconfirmationemail/' -H 'Authorization: Token 55c8da5de68b657cf9dafd820a7f02f997fa3d64' -H 'Origin: http://localhost:3000' -H 'Accept-Encoding: gzip, deflate, br' -H 'Accept-Language: en-GB,en-US;q=0.9,en;q=0.8' -H 'User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36' -H 'Content-Type: text/plain;charset=UTF-8' -H 'Accept: */*' -H 'Referer: http://localhost:3000/account' -H 'Connection: keep-alive' -H 'X-CSRFToken: YOHB6RXqpZMFIXKT31T9tAjaUsH0w3eIjaaCvbgAqmP64SWeVNd3Sz3g8nZUEmVS' --data-binary 'csrfmiddlewaretoken=YOHB6RXqpZMFIXKT31T9tAjaUsH0w3eIjaaCvbgAqmP64SWeVNd3Sz3g8nZUEmVS' --compressed
When I look at working examples from Django templates, I see that the csrfmiddlewaretoken value sent with the form data is not the same as the X-CSRFToken sent in the header - I think the value supplied by Django templates is salted and this might make a difference. But no instructions I can find tell me how to get the right value? Or do I have my fetch request in the wrong form somehow?
If I use this form in my React page:
<form action="api/v1/sendconfirmationemail" method="post">
<Input type="hidden" name="csrfmiddlewaretoken" value={this.getCookie('csrftoken')} />
<button type="submit">Send</button>
</form>
When I submit it, I get an error "Method Not Allowed (POST): /api/v1/sendconfirmationemail". The cURL from this request is:
curl 'http://localhost:3000/api/v1/sendconfirmationemail' -H 'Cookie: csrftoken=YOHB6RXqpZMFIXKT31T9tAjaUsH0w3eIjaaCvbgAqmP64SWeVNd3Sz3g8nZUEmVS; sessionid=uslpdgd5npa6wyk2oqpwkhj79xaen7nw' -H 'Origin: http://localhost:3000' -H 'Accept-Encoding: gzip, deflate, br' -H 'Accept-Language: en-GB,en-US;q=0.9,en;q=0.8' -H 'Upgrade-Insecure-Requests: 1' -H 'User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36' -H 'Content-Type: application/x-www-form-urlencoded' -H 'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8' -H 'Cache-Control: max-age=0' -H 'Referer: http://localhost:3000/account' -H 'Connection: keep-alive' --data 'csrfmiddlewaretoken=YOHB6RXqpZMFIXKT31T9tAjaUsH0w3eIjaaCvbgAqmP64SWeVNd3Sz3g8nZUEmVS' --compressed
Any idea how I can request an email resend from the React frontend?
I think part of my trouble was that the server seems to show the Forbidden (CSRF cookie not set.) error
if the fetch request has the incorrect URL and is therefore not pointing at a valid rest-framework URL. This had me chasing after CSRF issues instead of double checking my URL.
I hope this will help somebody else. Here's my working code:
users/views.py
from allauth.account.signals import email_confirmed
from django.dispatch import receiver
from rest_framework import generics
from rest_framework.permissions import IsAuthenticated
from allauth.account.utils import send_email_confirmation
from rest_framework.views import APIView
from . import models
from . import serializers
from rest_framework.authentication import TokenAuthentication
from rest_framework import status
from rest_framework.response import Response
class UserListView(generics.ListCreateAPIView):
queryset = models.CustomUser.objects.all()
serializer_class = serializers.UserSerializer
authentication_classes = (TokenAuthentication,)
# when the email is confirmed, set a field on the user
# so the UI can check whether to show the "Resend confirmation email" button
@receiver(email_confirmed)
def email_confirmed_(request, email_address, **kwargs):
user = email_address.user
user.email_verified = True
user.save()
# request a new confirmation email
class EmailConfirmation(APIView):
permission_classes = [IsAuthenticated]
def post(self, request):
if request.user.email_verified:
return Response({'message': 'Email already verified'}, status=status.HTTP_201_CREATED)
send_email_confirmation(request, request.user)
return Response({'message': 'Email confirmation sent'}, status=status.HTTP_201_CREATED)
api/urls.py
from users.views import EmailConfirmation
urlpatterns = [
...
path('sendconfirmationemail/', EmailConfirmation.as_view(), name='send-email-confirmation')
]
I'm sending a POST request using JavaScript fetch(), with the authentication token in the header like this:
-H 'Authorization: Token 98254e6004e4a28b9d8cf61e7a7a9ee2fc61009a'
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With