what is the best way to generate a reset token in python?

Why not just use a jwt as token for this purpose, its also possible to set an expiration time to it, so its also possible to put an expiration date to the token.

  1. Generate token(JWT) encrypted with a secret key
  2. Send mail containg a link with the token as a query paramater(When the user opens the link the page can read the token)
  3. Verify the token before saving the new password

For generating jwt tokens I use pyjwt. The below code snippet shows how it can be done with an expiry time of 24 hours(1 day) and signed with a secret key:

import jwt
from datetime import datetime, timedelta, timezone

secret = "jwt_secret"
payload = {"exp": datetime.now(timezone.utc) + timedelta(days=1), "id": user_id}
token = jwt.encode(payload, secret, algorithm="HS256")
reset_token = token.decode("utf-8")

Below snippet shows how the verification of token and the new password can be set in django. If the token has expired or has been tampered with, it will raise an exception.

secret = "jwt_secret"
claims = jwt.decode(token, secret, options={"require_exp": True})
# Check if the user exists
user = User.objects.get(id=claims.get("id"))
user.set_password(password)
user.save()

Not sure it's the best way, but I'd probably just generate a UUID4, which can be used in a URL to reset the password and expire it after 'n' amount of time.

>>> import uuid
>>> uuid.uuid4().hex
'8c05904f0051419283d1024fc5ce1a59'

You could use something like http://redis.io to hold that key, with a value of the appropriate user ID and set its time to live. So, when something comes in from http://example.com/password-reset/8c05904f0051419283d1024fc5ce1a59 it looks to see if it's valid and if so then allows changes to set a new password.

If you did want a "validation pin", then store along with the token, a small random key, eg:

>>> from string import digits
>>> from random import choice
>>> ''.join(choice(digits) for i in xrange(4))
'2545'

And request that be entered on the reset link.


Easiest way by far is to use the ItsDangerous library:

You can serialize and sign a user ID for unsubscribing of newsletters into URLs. This way you don’t need to generate one-time tokens and store them in the database. Same thing with any kind of activation link for accounts and similar things.

You can also embed a timestamp, so very easily to set time periods without having to involve databases or queues. It's all cryptographically signed, so you can easily see if it's been tampered with.

>>> from itsdangerous import TimestampSigner
>>> s = TimestampSigner('secret-key')
>>> string = s.sign('foo')
>>> s.unsign(string, max_age=5)
Traceback (most recent call last):
  ...
itsdangerous.SignatureExpired: Signature age 15 > 5 seconds