12

Im sending an email confirmation after a registration process in my django application. I need to find out for security reasons how can i validate the code im sending in the url without adding a new code field in the user model. So far im sending a random code in the url and the username which is validated but not the code.

Registration VIEW

def registrar_usuario_view(request):
alphabet = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
code = ''.join(random.choice(alphabet) for i in range(16))
print code
if request.method == 'POST':
    f = RegisterForm(request.POST)
    if f.is_valid():
        usuario = f.cleaned_data['usuario']
        email = f.cleaned_data['email']
        clave = f.cleaned_data['clave']
        confirmar_clave = f.cleaned_data['confirmar_clave']
        captcha = f.cleaned_data['captcha']
        u = User.objects.create_user(username = usuario, email = email, password = clave)
        u.is_active = False
        u.save()
        # Mandamos mail de activacion
        to = email
        html_content = """<h3>Bienvenido Sr/a: %s </h3><p>Para confirmar su registro en el sitio Margonari Servicios Inmobiliarios le solicitamos haga click en el siguiente 
        <a href='http://localhost:8000/confirmacion/%s/%s'>enlace de confirmacion</a><br><p><b>Gracias por formar parte de Margonari Servicios Inmobiliarios.</b></p><br>
        <small>Este es un mensaje enviado automaticamente. Por favor no responda a esta direccion de mail.</small>"""%(usuario, code, usuario)
        msg = EmailMultiAlternatives('Administracion Margonari', html_content, 'from@server.com', [to])
        msg.attach_alternative(html_content, 'text/html') #Definimos el contenido como html
        msg.send() #Enviamos el correo

        messages.add_message(request, messages.SUCCESS, """Los datos han sido ingresados correctamente. Le enviamos un correo de confirmacion 
            a la direccion que nos proporciono. Por favor verifique su casilla de correo no deseado. Muchas gracias.""")
        ctx = {'form':f}
        return render_to_response('users/registrar_usuario.html', ctx, context_instance = RequestContext(request))
    else:
        ctx = {'form':f}
        return render_to_response('users/registrar_usuario.html', ctx, context_instance = RequestContext(request))

f = RegisterForm()
ctx = {'form':f}
return render_to_response('users/registrar_usuario.html', ctx, context_instance =    RequestContext(request))

Confirmation VIEW

def confirmacion_view(request, code, user):
user = User.objects.get(username = user)
user.is_active = True
user.save()
return HttpResponseRedirect('/')

URL

url(r'^confirmacion/(?P<code>.*)/(?P<user>.*)/$', 'confirmacion_view', name = 'vista_confirmacion'),
Andrés Da Viá
  • 586
  • 1
  • 8
  • 24
  • Why not use some sort of cache (eg: memcache, retis, etc.) to store the registration "code" for the new user until it is validated? – Casey Falk Aug 13 '14 at 17:05
  • Also, please clarify your title to reflect the question -- not what you have already completed. :) – Casey Falk Aug 13 '14 at 17:06
  • Please use [django-registration](http://django-registration.readthedocs.org/) package if you are new to web development and do not want to make security mistakes with your site. – Mikko Ohtamaa Aug 13 '14 at 17:06
  • 1
    Is not recommended to use django-registration as it has not been maintained – Andrés Da Viá Aug 13 '14 at 17:16

1 Answers1

17

Django provides a token creation mechanism, there is no need to reinvent the wheel. Since I don't use function based views and the point here is not to refactor your code (I would do it in CBVs anyway), I will just output a sample on how you can use it.

from django.contrib.auth.tokens import default_token_generator
from django.utils.http import urlsafe_base64_encode
from django.utils.encoding import force_bytes

new_user = User.objects.create_user(username=usuario,
                                    email=email,
                                    password=clave)
new_user.save()
token = default_token_generator.make_token(new_user)
uid = urlsafe_base64_encode(force_bytes(new_user.pk))

You can then email the token to the user, the token url should look like:

url(r'^users/validate/(?P<uidb64>[0-9A-Za-z_\-]+)/(?P<token>[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/$',
    activationview,
    name='user-activation-link')

Somewhere in your activationview:

from django import http

uidb64 = request.GET.get('uidb64')
token = request.GET.get('token')

if uidb64 is not None and token is not None:
    from django.utils.http import urlsafe_base64_decode
    uid = urlsafe_base64_decode(uidb64)
    try:
        from django.contrib.auth import get_user_model
        from django.contrib.auth.tokens import default_token_generator
        user_model = get_user_model()
        user = user_model.objects.get(pk=uid)
        if default_token_generator.check_token(user, token) and user.is_active == 0:
            # Do success stuff...
            return http.HttpResponseRedirect(a_success_url)
    except:
        pass

return http.HttpResponseRedirect(a_failure_url)
ndmeiri
  • 4,979
  • 12
  • 37
  • 45
petkostas
  • 7,250
  • 3
  • 26
  • 29
  • I have updated my code with your response back which seems to be what i need but for some reason im not able to pass this validation if uidb64 is not None and token is not None: once i get an url like http://localhost:8000/confirmacion/MTQ/3u4-d6a9f8e53771675351a5/ this is my final code https://dpaste.de/J7Fm#L41 – Andrés Da Viá Aug 13 '14 at 19:56
  • 1
    @AndrésDaViá how you resolved it. I am facing issue. When i send email first time and click on verification link its throwing error due to token mismatch. Sending 2nd time verification link and clicking on link working perfectly. Error occurs due to this snippet inside token generator code `if not constant_time_compare(self._make_token_with_timestamp(user, ts), token): return False` Thanks – Lal Mar 13 '16 at 16:29
  • @Lal I found the same issue with using Django auth's `default_token_generator`. Many other [answers](https://stackoverflow.com/a/56116529/7446465) override `_make_hash_value()` to use pk, timestamp, and is_active instead. I *think* this works better because the default uses `login_timestamp` which wouldn't exist for an uncreated user – Addison Klinke May 27 '21 at 05:25