If you’re handling user authentication in your app, you’ve got two choices:
- Delegate it to an OAuth2 provider like Google or Facebook. These giants already have your users’ data, so why not let them do the heavy lifting?
- Take on the task of managing passwords yourself, because you’re either brave, naive, or work for a client who thinks OAuth2 is a type of shampoo.
If you’ve chosen the second option, congratulations! You now have to deal with password resets. This means giving users a way to securely reset their password when they inevitably forget it. Let’s discuss how this works under the hood.
Workflow
Here’s the high-level overview of how we’ll implement the “Forgot Password” feature:
- Create a JWT (JSON Web Token): This token will act as a temporary, signed credential to verify the user’s identity. It should have a short expiration time, because storing sensitive data in an email is like leaving your car unlocked in a bad neighborhood.
- Send the token via email: When the user requests a password reset, send them an email with the JWT embedded in a link.
- Reset password page: The email link will lead to a page where the user can enter a new password.
- Verify and update: The frontend submits the JWT and new password to the backend. If the JWT is valid, the backend updates the user’s password and everyone lives happily ever after (until the next bug report).

Now, let’s implement all this with Python and Flask.
Save New Password
First of all, let’s start by implementing the endpoint which accepts a new password from an authenticated request. To verify the received JWT, I use the library Flask-JWT-Extended.
@users_bp.route("/users/password/reset", methods=["POST"])
@jwt_required
def reset_password():
doc = request.json
new_password = doc["new_password"]
current_user = get_jwt_identity()
current_user.password = generate_password_hash(new_password)
db.session.commit()
return jsonify({}), 204
There is not much logic in there, I just validate the request and read the password from the request.
Let’s continue with an other endpoint, the one which sends the email with the JWT.
Send Authenticated Email
This endpoint, requires no authentication, as it will be used from an user who is unable to log in.
Inside, I first check the existence of the user with the given email address. Then, I create a JWT. And finally, I use the SMTP library to send an email.
@users_bp.route("/users/password/forgot", methods=["POST"])
def forgot_password():
doc = request.json
user = MyUser.query.filter(MyUser.email == doc[""]).one_or_none()
if not user:
raise Error("Unknown user")
encoded_jwt = jwt.encode(
{
"sub": doc["email"],
"exp": int(time.time()) + (60 * 60),
},
"my-secret-key",
algorithm="HS256",
)
body = f"""
To reset your password, click <a href="https://myfrontend.com?jwt={encoded_jwt}">here</a>.
"""
try:
msg = EmailMessage()
msg.set_content(body)
msg['Subject'] = "Reset your password"
msg['From'] = "no-reply@enterprise.com"
msg['To'] = doc["email"]
# Connect to Gmail SMTP server
with smtplib.SMTP_SSL('smtp.gmail.com', 465) as smtp:
smtp.login(from_email, app_password) # Authenticate
smtp.send_message(msg) # Send the email
print("Email sent successfully!")
except Exception as e:
print(f"Failed to send email: {e}")
return jsonify({}), 204
Conclusion
The main goal of a “Forgot Password” feature is to securely authenticate the user through their email. By using a short-lived JWT, you minimize risks while ensuring the process remains user-friendly. Remember:
- Keep JWTs short-lived to reduce the impact of exposure.
- Use HTTPS for all communications (because plaintext HTTP is so 1990s).
- Educate your users that “password” is a terrible password.
Implementing this feature might feel like extra work, but it’s essential for user trust.



Leave a comment