abhimanyuPathania abhimanyuPathania - 2 months ago 9
Python Question

Verifying user accounts using email (Google App Engine)

I am building custom accounts system, where when a user creates an account I save it to Datastore but with the 'verified' field default set to false (one cannot login with an Account until its verified field is set to true.)

class Account(ndb.Model):
username = ndb.StringProperty(required = True)
password = ndb.StringProperty(required = True)
email = ndb.StringProperty(required = True)
verified = ndb.BooleanProperty(default = False)
created = ndb.DateTimeProperty(auto_now_add = True)


'username' in my app is supposed to be unique and important. I am setting the 'id' of the entity to the username and later use get_by_id method to fetch the account in all other operations.

acc = Account( id=username,
username = username,
password = password,
email = email)


When I save the account, I convert its key to string using the uslsafe.

urlString = rev_key.urlsafe()


I am using this string to build a URL and mail it to the user, and later on request from that URL use the uslString to construct my key back and turn 'verified' field to true.

But this exposes my actual key and it might expose usernames. I have a couple of questions regarding this.


  1. How do keys exist in datastore. If someone has the 'urlString' can anyone construct my key and fetch my entity? Or entities are only accessible to the app that made them?

  2. How to do this more securely?

  3. We can hash the string before we send it. But then how to recover my key from it? Should I save the hash itself along with other fields and use it to fetch the account for authentication.

  4. Which hashing scheme would be suitable for this?


Answer

The keys will only work on your app, the app-id is actually part of the full key; but as you imagined, anyone could construct a key for different usernames and thus validate them, even if they didn't got the email. As noted in the comment by @Paul, you shouldn't trust that the key hasn't been tampered with.

I advise you to handle verification separately, for example:

import hashlib
import os

class Verification(ndb.Model):
    account = ndb.KeyProperty()

    @classmethod
    def create(cls, account):
        verification = cls(
            id=hashlib.sha1(os.urandom(16)).hexdigest(),
            account=account.key,
        ).put()
        return verification.id()

    @classmethod
    def validate(cls, verification_id):
        verification = cls.get_by_id(verification_id)
        account = verification.account.get()
        account.verified = True
        account.put()

What we did here is create a different model, Verification, to handle this feature; when created, this entity gets an obscure id, that you can then use to validate the right account. You could build on this to handle app-specific features.