I wanted to put this out here and see if there were any security vulnerabilities that I'm not seeing. Also, if I can improve performance without sacrificing security, that's always a plus. Thanks in advance!Typically, when you want to validate an e-mail address, you store an activation key and expiration date in the user profile table. So you can validate it one time. Then what? Just keep storing it forever?  Clear it out and have the empty columns? Write handlers so this data doesn't show up the user profile? I wanted to use a disposable key that didn't have to be stored in a database. This is my solution; which is also available on gist ...# user/helpers.pyfrom django.core.signing import BadSignature, Signerimport base64import jsonimport timedef email_validation_encode(payload):        I expect the payload to be something like:         {            'email': 'me@example.com',             'expires': (a few days from now),        }    However, I don't care enough to validate        payload_json = json.dumps(payload)    payload_b64 = base64.b64encode(payload_json.encode('utf-8'))    signer = Signer()    return signer.sign(payload_b64.decode('utf-8'))def email_validation_decode(string):        Expects the result from the `email_validation_encode` function.    If the string looks valid: returns the original payload    If anything is changed/invalid: returns False        signer = Signer()    try:        string_b64 = signer.unsign(string)    except BadSignature:        return False    json_decoded = base64.b64decode(string_b64).decode('utf-8')    return json.loads(json_decoded)def create_email_validation_key(email):        Makes an object:        {            'email': 'me@example.com',             'expires': (two or three days from now),        }    returns the key to put in the link to send to email        return email_validation_encode({        'email': email,        'expires': (time.time() + 60*60*24*2),    })def decode_email_validation_key(key):        This takes the key (from link sent to email), sends it to be decoded.    Then, it checks the expiration.    If the key is valid and hasn't expired: returns just the email address    If anything is changed/invalid: returns False        decoded = email_validation_decode(key)    if not decoded or time.time() > decoded['expires']:        return False    else:        return decoded['email']def get_uri(request, force_secure=False):        Get the current URI; ie: http://localhost        if force_secure or request.is_secure():        return 'https://%s' % request.get_host()    else:        return 'http://%s' % request.get_host()ImplementationImplementation would obviously be within my user/views.py file, but you'll get the idea from this excerpt, I think:C:\Temp>env\Scripts\python.exe manage.py shellPython 3.5.1 (v3.5.1:37a07cee5969, Dec  6 2015, 01:54:25) [MSC v.1900 64 bit (AMD64)] on win32Type help, copyright, credits or license for more information.(InteractiveConsole)>>> from user.helpers import create_email_validation_key, decode_email_validation_key, get_uri>>> import urllib.parse>>>>>> key = create_email_validation_key('VertigoRay@example.com')>>> key'eyJlbWFpbCI6ICJWZXJ0aWdvUmF5QGV4YW1wbGUuY29tIiwgImV4cGlyZXMiOiAxNDYzNDU2MzgwLjA4NDAyNjN9:xzklS0YL3NT-GR33Nilo'>>> >>> url = 'http://example.com/user/register/{0}'.format(...   urllib.parse.quote(key),... )>>> url'http://example.com/user/register/eyJlbWFpbCI6ICJWZXJ0aWdvUmF5QGV4YW1wbGUuY29tIiwgImV4cGlyZXMiOiAxNDYzNDU2MzgwLjA4NDAyNjN9%3AxzklS0YL3NT-GR33Nilo'>>>>>> # That's the url that we'll send via email>>> # When a user clicks the link, `urls.py` will send the key portion as `key`; such as:>>> #     url(r'^validate_email/(?P<key>[a-zA-Z0-9-_=]+[:][a-zA-Z0-9-_=]+)$', validate_email, name='validate_email'),>>>>>> key'eyJlbWFpbCI6ICJWZXJ0aWdvUmF5QGV4YW1wbGUuY29tIiwgImV4cGlyZXMiOiAxNDYzNDU2MzgwLjA4NDAyNjN9:xzklS0YL3NT-GR33Nilo'>>> email = decode_email_validation_key(key)>>> email'VertigoRay@example.com'>>> >>> # At this point, we can lookup the account with this e-mail address >>> # and mark it as validated in the database.>>>>>> # One more thing, let's break the key:>>> email = decode_email_validation_key(key[1:])>>> emailFalseNote: I would actually set the url as follows, but I wanted to just focus on the functions related to this demo. I'm only showing you this additional bit to demo the usage of the get_uri() function; in case you can give me a better way to do this:url = '{0}/{1}/{2}'.format(    get_uri(request),    reverse('register'),    urllib.parse.quote(key),)Update: changed from pickle to json (https://youtu.be/7KnfGDajDQw). I tend to prefer json, but I didn't want to convert datetime.datetime.now() to something json would parse. A good friend told me to use time.time(), and I felt like a n00b. I'm enjoying the shorter keys as well.
Validating Email Addresses without DB
python;python 3.x;validation;email;django
As Sjoerd points out in comments, the timestamp generation and checking logic in this code is reimplementing Django's built-in TimestampSigner. Standard implementations are more likely to be correct (they've had more review and testing) and they are more likely to get fixed quickly if problems are discovered.Similarly, the serialization and deserialization logic in this code is reimplementing Django's built-in signing.dumps and signing.loads.decode_email_validation_key returns a string (the e-mail address) if verification is successful, or False if it fails. This is risky: it would be easy to forget to check the result. It would be better to raise an exception in case of failure. Then if you forget to check the exception, it doesn't open a security hole.The expiry time (here, 60*60*24*2) should have a name, and it should be defined in the application settings file, since you'll probably want to use it in the construction of the e-mail text to tell the user when the link expires.Revised code (settings file):from datetime import timedeltaEMAIL_KEY_EXPIRY_TIME = timedelta(days=2)Revised code (view file):from myapp.settings import EMAIL_KEY_EXPIRY_TIMEfrom django.core import signingcreate_email_validation_key = signing.dumpsdef decode_email_validation_key(key):    return signing.loads(key, max_age=EMAIL_KEY_EXPIRY_TIME)