19

I am using Django with Django REST framework as a backend and AngularJS on frontend.

For the user management I am using django-rest-auth which uses django-allauth for the user management. As the base I used demo from django-rest-auth.

The problem is after the sign up when you try to verify the email it sends email with activation url: 127.0.0.1:8000/account/confirm-email/yhca8kmijle0ia7k3p7ztbvnd2n1xepn9kyeuaycmlzll5xw19ubjarnvjrot7eu/

where *127.0.0.1:8000 is the Django backend.

But in my case it should send url something like localhost:9000/#/verifyEmail/akv2dcvrfnk9ex5fho9jk0xx1ggtpfazmi8sfsoi2sbscoezywfp7kzcyqnizrc0. So that this verification will be done from frontend, where localhost:9000 is my AngularJS frontend.

Is there any way of customizing activate_url on django-allauth?

ferrangb
  • 2,012
  • 2
  • 20
  • 34
mipasov
  • 293
  • 2
  • 8
  • Did you see this question? https://stackoverflow.com/questions/24128433/django-allauth-how-to-modify-email-confirmation-url – ferrangb Jan 19 '15 at 10:39

3 Answers3

31

I have managed to make the activation from the frontend by overriding the DefaultAccountAdapter class and overriding the send_mail method as specified in the django-allauth doc :

If this does not suit your needs, you can hook up your own custom mechanism by overriding the send_mail method of the account adapter (allauth.account.adapter.DefaultAccountAdapter)

I first wrote the backend url that I use to confirm an email :

api_patterns = [
...
url(r'^verify-email/(?P<key>\w+)/$',
        confirm_email, name="account_confirm_email"),
...
]

I then specified the custom adapter and the frontend url in settings.py as follows :

ACCOUNT_ADAPTER = 'API.adapter.DefaultAccountAdapterCustom'
URL_FRONT = 'http://localhost:9000/'

And wrote this in the adapter.py from my app :

from allauth.account.adapter import DefaultAccountAdapter
from django.conf import settings

class DefaultAccountAdapterCustom(DefaultAccountAdapter):

    def send_mail(self, template_prefix, email, context):
        context['activate_url'] = settings.URL_FRONT + \
            'verify-email/' + context['key']
        msg = self.render_mail(template_prefix, email, context)
        msg.send()

The activation url sent in the email will now look like : http://127.0.0.1:9000/verify-email/<key>/

I changed the activate_url to the URL_FRONT string I specified in settings.py and appended the key in order to make a get request from the frontend to the url I wrote earlier (Let's say http://localhost:8000/verify-email/<key>). (Previously setting ACCOUNT_CONFIRM_EMAIL_ON_GET to True in order to confirm an email just by doing a get request)

Storm
  • 717
  • 6
  • 11
  • This is the simpler and best solution I found. Many answers use allauth views to display screens to validate email. But this should be handled by your frontend, because there is a nonsense for the rest API to serve templates. This solution enable the possibility to send an email containing the @ of your frontend which THEN will hit the rest API. Thanks Storm! – stockersky Jan 29 '19 at 11:20
  • 1
    Using Vue: I let /verify-email/:confirmationKey pass through to my Vue router, from there I render a component that in turns pings the rest-auth api to confirm the key. Works great – Johnny O Mar 30 '19 at 01:49
  • Awesome! But could you explain how to use a similar approach for password reset? – glezo Jul 11 '21 at 15:00
5

Couldn't find any docs specifying this (sure they must be out there somewhere), but looking at github they just do a URL reverse on "account_confirm_email"

You just need to add an urlpattern with the name="account_confirm_email" param after theirs, like so:

from allauth.account.views import confirm_email

ulrpatterns= patterns('',
    ...
    url(r'^rest-auth/', include('rest_auth.urls')),
    url(r'^verify-email/(?P<key>\w+)/$',
        confirm_email,
        name="account_confirm_email"),
)

The problem with this, is that you are now directly addressing the dependency of a dependency. If a newer version of django-restauth doesn't use this, it could break on you.

If you are open to using other libraries, I'm currently using djoser with DRF and have found it pretty straightforward. Perhaps it meets your needs.

Tom Manterfield
  • 6,515
  • 6
  • 36
  • 52
  • This should be marked as the correct answer as it is clearly stated in ```dj-rest-auth/registration/urls.py```. The view ```confirm_email``` is just a ```ConfirmEmailView.as_view()```. I guess this dependency wouldn't change easily as this answer dates back to 7+ years ago yet still the formal method. – O. Salah Oct 16 '22 at 22:00
5

Building on Storm's answer I have a slight improvement (perhaps this wasn't possible when the answer was originally posted). DefaultAccountAdapter has a specific get_email_confirmation_url method that can be overridden to generate the email verification URL and nothing else. The following works well for me. There is no need for an entry in urls.py.

In settings.py

# Repoint this setting to a subclass of the DefaultAccountAdapter 
# so we can override how it handles account verification to allow 
# for usage in an SPA context.
ACCOUNT_ADAPTER = 'common.accountadapter.CustomAccountAdapter'
# An email verification URL that the client will pick up.
CUSTOM_ACCOUNT_CONFIRM_EMAIL_URL = "/verifyemail/?key={0}"

In common.accountadapter.py:

from allauth.account.adapter import DefaultAccountAdapter
from allauth.utils import build_absolute_uri
from django.conf import settings

class CustomAccountAdapter(DefaultAccountAdapter):

    def get_email_confirmation_url(self, request, emailconfirmation):
        url = settings.CUSTOM_ACCOUNT_CONFIRM_EMAIL_URL.format(emailconfirmation.key)
        ret = build_absolute_uri(
            request,
            url)
        return ret

In my case, django is rooted at "/backend", and a javascript client (in Vue) is rooted at "/". This returns a URL like this, which is handled by the javascript client:

http://localhost/verifyemail/?key=MTc:1h4C9w:b0CIOekBNfirVSt-0-bHOSQQnjk

(localhost is automatically populated from the hostname from the request).

Blanch
  • 51
  • 1
  • 2