Learn how to create the functionality to reset the passwords of your users.
After reading this article you'll learn how to:
-
Create a forgot password page
-
Send an email to a user who forgot their password
-
Reset a password properly
Getting started
Before we dive deeper into the forgot password functionality, let's make sure we're on the same page. This use case that'll tackle is a continuation of the creating the register functionality for webusers article.
We won't be going into the basics of the application, the basics are all handled in the article mentioned above. You don't need an exact copy of the application yourself but the basics must be there, primarily a WebUser model like the one described in the article above.
Expanding the data model
We'll need to add some properties to our data model to make sure the forgot password functionality is working as expected. The properties that are an absolute must-have are as followed:
-
Password 'Password'
-
Number, 'Token expiration', format:
[].
-
Text (single line), 'Token'
With these properties added to the WebUser model we can start building.
Creating the pages
If you've followed the article for registering users then you already have a login page present. If you don't have one yet make sure to create one. We'll first create a new page that'll trigger the forgot password flow.
Let's use the header and footer template, the name of this page will be 'Reset password request'.
After creating the page let's add a link to it on our login page. Navigate to the login page and add a text component that links to the internal page '/reset-password-request'.
Back to our reset password request page. Let's create the request, for this add a column to the page and in that column add a form component.
We'll only need the email address property. To make the page a bit prettier, change the text of the 'send' button to something more appropriate like 'send recovery link'. And add a text under the button like we just did on the login page but this time do it the other way around, redirect the user back to the '/login' page when he clicks it. The by default generated text is also a bit misleading, change it to something like 'An email with the password reset information has been sent to this email address.' Indicating that the user has to check his email address, alright let's create the functionality behind this now!
Password reset action
Navigate to the action of the form we just created, either go to the actions tab and select it from there or click the edit button when you have the form selected on your page.
Select the Start step, the email_address input variable is there already from our form. Create a new record variable called existing webuser and filter on email address.
Next up we'll need an expression function, this isn't a default function so make sure to go to the block store and install it.
After installing this you should have the following custom functions in your application:
-
Expression
-
Send e-mail via SMTP
-
Raise error
-
Generate random hex
Install the others as well if you haven't already. Now make sure we're in our action, and drag the expression function into the flow. Configure the flow like this:
We're first creating a variable called webuser with the value existing_webuser that we just created previously. In the expression we then check whether the value of the webuser is true or false, implying that we did or didn't find one.
"" ? true : false
The output type is a checkbox that we name webuser_exists, we'll use this in the next step.
Next up, drag a condition step under the expression step. Select the webuser_exists variable to determine the condition flow.
Save the step, and rename 'true' to 'webuser found' and 'else' to 'webuser not found' this makes the action flow easier readable. When we can't find a webuser we want to show an error. So drag the raise error step in the webuser not found flow, as the message we can put something like 'User not found.' that'll make it clear to the user.
For the webuser found flow, drag a generate random hex step in there. Put the size to 36 or so, and name the output of the step random_hex. The next step we need to add is the expressions step. Here add the following expression that is the current time + 1 hour.
Math.round((new Date()).getTime() / 1000) + 3600;
The output is a number that we'll call now. And that's that for the expression.
Next up in the webuser found flow, updating the webuser record. Drag an update step underneath the expression. Select the existing_webuser variable as a record to be updated. The values that we want to upload are the token and the token expiration properties, add those. Add the random_hex variable to the token and add the now variable to the token expiration property.
The output will be the updated_webuser.
Now we're done with the webuser found flow, let's finalize the action by sending an email to our user. Drag the send e-mail via SMTP step above the finish step. If you're not sure how to configure an email make sure to check out this article explaining how you can configure use the SMTP step. Add a webuser record variable to the action step, this will be used in the step to retrieve the webuser's name and token.
After creating the variable make sure the proper configurations are in place in the send e-mail via SMTP step. In this use case, I'll use Etherial Mail so with that in place all we need to do is add the webuser his credentials and create the email body. For the body add the first_name variable with the webuser his first name as the value. And add the token variable with the value of the webuser his token. The body will result as followed:
Dear ,<br><br>
Please click on the following <a href="https://[APPLICATION_ID].betty.app/reset-password/">link</a> to reset your password.<br><br>
With kind regards,<br>
[YOUR_NAME or COMPANY_NAME]
The application id is the name of your application, don't forget to change it.
Save it as email_output and we can call it a day for this action.
You might've noticed that we're using a link '/reset-password/' in the email body, but this page doesn't exist yet. Let's change that, shall we?
Resetting password page
This is the page that a user will land on after clicking the link inside the email created above. The user has to type his password once and then a second time to confirm his password. After this, we'll update the password for the user after which we'll redirect him to the login page.
To get started let's create this page, select the header and footer template, and make sure to name the URL exactly like the image below. Using a colon :
in the URL means that it'll contain an input variable in our case the input variable is the token.
The first thing we'll do is we'll add the input variable from the URL as a variable that we can use on our page. Click on the variables tab of the page and add a text variable called token.
Next let's drag a column component and in that a form component. Disable the 'model based form'.
Delete the by default generated input and drag two password inputs in the form from the component list. A user will need to confirm his password in the second field to prevent the situation where he makes a typo and has to re-reset his password a second time. Give the first password input the name new_token and the second one comfirm_password. Now edit the label of the two so the form looks somewhat similar to this:
To wrap the form up, drag a hidden input somewhere inside the form. Name the action input variable token, and hit save. Now add the input variable that we created as the value of this action input variable like so:
So when a user clicks the mail with his unique token in the URL he'll land on this page. The unique token, which is only valid for one hour, will be inside the hidden input. His new password will be in the two password inputs. All three of them will be sent to the action attached to the form.
Resetting the password
Navigate to the 'reset password' action, either by going there via the form we just created or via the actions overview.
Inside the 'reset password' action, let's start off with creating a webuser record. Click on the start step, the following three input variables should be present:
-
token
-
new_password
-
confirm password
Click on the action variable tab and create a new webuser based on the token input variable like so:
The next step is to verify if the token of this webuser is still valid. Drag an expression step in the action flow. Use the following expression:
Math.round((new Date()).getTime() / 1000)
The output type is 'number' and the let's name the variable now. Basically the variable contains the current time, we'll compare this variable against the expiration date of the webuser that we created previously. If it's older than an hour we'll display an error message and when the token is valid (within one hour) we'll proceed with the reset of the password.
Add another expression step after the one we just created. Add two variables, one called token_expiration with the value of the webuser's token expiration, and one called now with the value now that we created in the previous step. We can use the following expression to see if now is within an hour of token_expiration.
<= ? true : false
The output type of this expression is a checkbox called token_valid.
Alright, now we can use the token_valid in a condition step. Drag a condition step under the second expression we just created. Configure the expressions as followed:
In the 'token is not valid' path add a raise error step with the message that the token isn't valid. In the 'token is valid' flow we'll check if the passwords that we retrieve from our page are a match. Drag an expression property in the valid token flow, add two variables new_password and confirm_password and assign the variables with the same name to them. We can then use the following expression to check if the passwords match.
"" === "" ? true : false
The output toye that we want to use is, again, a checkbox. Name this checkbox password_match.
With this added we'll add another conditional right after this expression, we'll display an error if the passwords don't match and we'll update the password of the user if they do match!
So drag a conditional step underneath the expression. In this conditional step check the following:
Drag an error message in the 'passwords don't watch flow' with a message saying that the passwords do not match.
In the 'password match' flow drag an update record step into it. Select the webuser variable as record, add the password property with the new_password as value and add the token property that'll leave empty.
And that's it for the action part. If a user has a valid token and his passwords match, it'll be updated. This is how the final action looks:
To make this a bit nicer I'd say we should redirect the user to the login page now once he succeeded to update his password.
Finalizing the flow
After a successful password reset, we should redirect the user to the login page. But of course, we'd also like to display an alert confirming that the password was updated. Navigate to the login page and add an input variable called 'password_reset'.
Now drag a conditional component on the page and configure it like the image below, password_reset is equal to 'Y'. Drag an alert box in the condition saying that the password reset was successful.
Navigate to the 'reset password' page and select the form. Go to the interactions tab and add a new interaction, configure it like this:
With this interaction in place, when the action runs successfully users get redirected to the login page. An alert will inform them that their password is reset and that they can log in.