Password Reset with Flask-Mail Protocol

Steven Monaghan
4 min readJan 25, 2020

One of the features that I have recently been working on in my flask application is setting up a password reset page. This is a necessary feature for any application that needs to track user accounts. In a recent study, HYPR found that 78% of full-time workers in the US and Canada needed to rest their password in the last three months. It is apparent that we need to implement a protocol to handle forgotten passwords!

Flask-Mail

The most common way to allow users to reset passwords is by email. Fortunately, there is a package called flask-mail that provides us with the capacity to send out emails. We can easily import and configure this package in our flask app to send out any automated emails that we desire. Simply run the following to get setup:

pip install flask-mail

Once the package is installed we need to import the Mail and Message classes into our application. The Mail class contains the configuration that will allow you to sync up with whichever email server you choose. As an example, we establish this setup after creating our app as in below:

app = Flask(__name__)
app.config['MAIL_SERVER'] = 'smtp.gmail.com'
app.config['MAIL_PORT'] = 465
app.config['MAIL_USE_SSL'] = True
app.config['MAIL_USERNAME'] = "username@gmail.com"
app.config['MAIL_PASSWORD'] = "password"
mail = Mail(app)

Note that if you use gmail, like I have in this example, that if you run this without any prior configuration on gmail you will receive the following error: smtplib.SMTPAuthenticationError. Gmail requires you to either setup 2FA or to allow ‘less secure’ applications to access your email account.

After your mail object is properly configured, we can move on to sending an email. Emails can be constructed by using the Message class that we have imported. Instantiate the object, set the parameters, and then send with the mail object:

msg = Message()
msg.subject = "Email Subject"
msg.recipients = ['recipient@gmail.com']
msg.sender = 'username@gmail.com'
msg.body = 'Email body'
mail.send(msg)

Once this code is called, an email will be sent to recipient@gmail.com with the parameters you have supplied. You can also include cc, bcc, attachments, and even send an HTML message. Check the documents to see all of the possibilities.

The last thing to mention is that this process runs synchronously, meaning that the application will stall until the email is sent. This can be frustrating for users. A workaround is the python Thread module to send the request asynchronously and free up the user. This can be setup as below:

from threading import Thread

def send_email(app, msg):
with app.app_context():
mail.send(msg)
msg = Message()
msg.subject = "Email Subject"
msg.recipients = ['recipient@gmail.com']
msg.sender = 'username@gmail.com'
msg.body = 'Email body'
Thread(target=send_email, args=(app, msg)).start()

Password Reset

There are many ways that one can handle a password reset feature. Here I am going to JWT (JSON Web Token) in order to verify the user. You can find a bare bones application I built to demonstrate this idea here. In order for the user to reset their password the following steps will have to be taken:

  1. The user submits their email to a password reset page.
  2. The application verifies the user exists in the database and sends the user a link to reset their password.
  3. The user clicks on the link, and this sends them to the password reset page. The GET request sent with this click will also issue the user a password reset JWT to verify that they can access the page.
  4. The user can now submit their new password.

The first step here involves establishing a page to allow the user to request a password reset. I’ve constructed a simple page like this:

Once the user enters their email and hits submit, the flask application will receive a POST request with the email contained in the form. We verify that the email submitted is in the database, and then we send the user an email that contains the JWT associated with their account. This can be generated like such,

models.py
...
def get_reset_token(self, expires=500):
return jwt.encode({'reset_password': self.username,
'exp': time() + expires},
key=os.getenv('SECRET_KEY_FLASK'))
email.py
...
def send_email(user):
token = user.get_reset_token() msg = Message()
msg.subject = "Flask App Password Reset"
msg.sender = os.getenv('MAIL_USERNAME')
msg.recipients = [user.email]
msg.html = render_template('reset_email.html',
user=user,
token=token)
mail.send(msg)

It is important that access to the reset page is restricted only to the bearer of the JWT. This ensures that only the account owner can reset the password for their account. Once the user receives the email and clicks on the link, they will be taken back to a page where they can reset their password if the JWT matches. This can be implemented like such:

def verify_reset_token(token):
try:
username = jwt.decode(token,
key=os.getenv('SECRET_KEY_FLASK'))['reset_password']
except Exception as e:
print(e)
return
return User.query.filter_by(username=username).first()

If they JWT returns a user then they are taken to the following page:

Here the user can type in their desired password. This will make a post request and the application will update the user table with the new updated password. The password reset is completed!

--

--

Steven Monaghan

Hello, I am Steven Monaghan. I work professionally writing software that provides statistics and machine learning to users. Reach me: http://stevenmonaghan.com