Password Reset

Any user management application needs a way for the users to reset their password. While Form.io does provide an Action dedicated to Reset Password, manytimes you may need more granular control over the process of resetting a users password. This guide will outline how this can be accomplished.

To accomplish this task, we will be using the Human Resources Application application as a reference application. If you wish to clone that project and application to follow along how to create this workflow, then type the following within your terminal to bootstrap the application.

npm install -g formio-cli
formio bootstrap formio/formio-app-humanresources

Once you do this, you can then see in the application all of the elements this tutorial walks you through.

Sending Reset Password

The first step is to present to the user a page within the application to reset their password. In this page, they will see a simple form where they will provide their Email address to then send an email. We can create that form within Form.io by creating a new Form call Send Reset. Within this form, we will also add a hidden field which will be the Application URL. This will be useful when adding that to the email template on where to send the user when they click on the Reset Password link. The form we are building will look like the following.

Now that you have created this form, you will need add an Email Action which will trigger a SSO token for the user to login. For the Message body, we will provide the following.

<a href="{{ data.applicationUrl }}/?token=[[token(data.email=employee)]]#resetpass">Click here to reset your password</a>

This uses the Login with Email capabilities discussed in the Developer section. Here is what that email action configuration looks like.

We now need to Save Action. We will then remove the Save Submission action (since we don’t really care about saving the reset password events).

Next, we need to ensure that Anonymous users can submit this form since they will be logged out when they need to reset their password. We can do this easily within the Access section of our form.

Make sure you press Save Changes so that the changes take effect.

Now that we have the form created, we will now add it to our Human Resources application. Within your project, in the /src/config.js file, we will add the following to the forms section.

angular.module('formioApp').constant('AppConfig', {
  appUrl: APP_URL,
  apiUrl: API_URL,
  forms: {
    userLoginForm: APP_URL + '/user/login',
    sendResetPassword: APP_URL + '/sendreset'
  },
  resources: {
    employee: {
      form: APP_URL + '/employee',
      resource: 'EmployeeResource'
    }
  }
});

We now need to register a state within the Auth state that will allow them to reset their password. We can do this within the /src/app/index.route.js file as follows. Don’t copy the ... text.

    $stateProvider
      ...
      ...
      .state('auth.sendreset', {
        url: '/sendreset',
        templateUrl: 'views/user/sendreset.html',
        controller: ['$scope', function($scope) {
          $scope.submitted = false;
          $scope.submission = {data: {applicationUrl: location.origin}};
          $scope.$on('formSubmission', function(event, submission) {
            $scope.submitted = true;
          });
        }]
      });

And then, within our /src/views/user/sendreset.html file, we will add the following.

<div class="well" ng-if="!submitted">
  <h3>To reset your password, provide the following...</h3>
  <formio src="sendResetPassword" submission="submission"></formio>
</div>
<div class="well" ng-if="submitted">
  <h4>Thank you! We have sent you an email with a link to reset your password.</h4>
</div>

Notice in both the controller, as well as the template, that we are switching the UI so that it shows a confirmation to the user when their password has been reset. This is a nicety that adds to the user experience. You may have also noticed that we are setting the applicationUrl of the hidden field within the form. This is so that when the form is submitted, it will include that data along with the submission, which will then get passed along to the email action which will establish the correct callback url to the application.

We now need to add the forgot password link to the Authentication block, which will shift between showing the “Reset Password” link and “Login Link” depending on which state they are in. This will look like the following.

/src/views/user/auth.html

<div class="col-md-8 col-md-offset-2">
  <div class="panel panel-default">
    ...
    ...
    ...
    <div class="panel-footer">
      <a ui-sref="auth.sendreset()" ng-if="isActive('auth.login')">Forgot your password? Click here.</a>
      <a ui-sref="auth.login()" ng-if="isActive('auth.sendreset')">Login</a>
    </div>
  </div>
</div>

This will now work like the following.

When they put in their email, they will now receive an email to reset their password. Once they click on that link, they will be redirected back to the application with a SSO token attached to the URL. Here we will have the user reset their password.

Reset Password Form

Before we complete the workflow from the application perspective, we will first need to create a new form within Form.io that will serve as the Password Reset form. This can be accomplished by creating the following form.

The Verify Password field has a custom validation configured with the following rule.

valid = (input == '{{ password }}') ? true : 'Passwords must match';

Once you save this form, go ahead and remove the Save Submission action since we are not using this form to save any data.

You don’t need to worry about setting up Access permissions since we are not really using this form for submission, but rather to render the form in the application.

We can now add this form to our application within the /src/config.js file as follows.

angular.module('formioApp').constant('AppConfig', {
  appUrl: APP_URL,
  apiUrl: API_URL,
  forms: {
    userLoginForm: APP_URL + '/user/login',
    sendResetPassword: APP_URL + '/sendreset',
    resetPassForm: APP_URL + '/resetpass'
  },
  resources: {
    employee: {
      form: APP_URL + '/employee',
      resource: 'EmployeeResource'
    }
  }
});

We are now ready to handle the token response from email.

Resetting the Password

Notice in the previous section, we had the user click on the following link.

<a href="{{ data.applicationUrl }}/?token=[[token(data.email=employee)]]#resetpass">Click here to reset your password</a>

Within this URL is a HASH parameter to take the user back to the application under the *resetpass state. After they click on that link, they will be taken back to the application where that state is triggered, and the token is passed along to the application. The first step is to handle that token within the application so that their user is established. We can do this within the /src/config.js file by adding the following code.

// Parse query string.
var query = {};
location.search.substr(1).split("&").forEach(function(item) {
  query[item.split("=")[0]] = item.split("=")[1] && decodeURIComponent(item.split("=")[1]);
});

if (query.token) {
  localStorage.setItem('formioToken', query.token);
  localStorage.removeItem('formioAppUser');
  localStorage.removeItem('formioUser');
  window.history.pushState("", "", location.pathname + location.hash);
}

This bit of logic looks for the token passed to the application, and then if it is found, set the user to that token, and then reset the url to not show the token anymore. This will make it so your application cleanly goes to the state provided above, while at the same time authenticate the user. From, here we need to create a state where they land to reset their password, which we can add into the /src/app/index.route.js file.

$stateProvider
      ...
      ...
      .state('auth.sendreset', {
        url: '/sendreset',
        templateUrl: 'views/user/sendreset.html',
        controller: ['$scope', function($scope) {
          $scope.submitted = false;
          $scope.$on('formSubmission', function(event, submission) {
            $scope.submitted = true;
          });
        }]
      })
      .state('resetpass', {
        url: '/resetpass',
        templateUrl: 'views/user/resetpass.html',
        controller: ['$scope', function($scope) {

        }]
      });

We can now create a new template to render the form we added in a previous section, which is as follows.

/src/views/user/resetpass.html

<div class="col-md-8 col-md-offset-2">
  <div class="panel panel-default">
    <div class="panel-heading">
      <h3 class="panel-title">Reset Password</h3>
    </div>
    <div class="panel-body">
      <h4>Please provide your password to reset.</h4>
      <formio form="form"></formio>
    </div>
  </div>
</div>

Notice that we are not passing the src of the form to the <formio> directive. The reason for this is because we are not wanting to submit the form that is rendered. By default the src parameter will not only render the form, but then also submit to the API once they press submit. By passing the form JSON object into the form, we are telling the renderer that we just wish to render the form, which we will then handle the response manually. We can do this within the controller as follows.

.state('resetpass', {
    url: '/resetpass',
    templateUrl: 'views/user/resetpass.html',
    controller: ['$scope', 'Formio', '$rootScope', 'AppConfig', function($scope, Formio, $rootScope, AppConfig) {
      $scope.form = null;
      (new Formio(AppConfig.forms.resetPassForm)).loadForm().then(function(form) {
        $scope.form = form;
      });
    }]
  });

We now need to handle the submission of the form, which we will then set the $rootScope.user object password, and then Save the user back to the API. This can be done like the following.

.state('resetpass', {
    url: '/resetpass',
    templateUrl: 'views/user/resetpass.html',
    controller: [
      '$scope',
      '$state',
      'Formio',
      '$rootScope',
      'AppConfig',
      function(
        $scope,
        $state,
        Formio,
        $rootScope,
        AppConfig
    ) {
      $scope.form = null;
      (new Formio(AppConfig.forms.resetPassForm)).loadForm().then(function(form) {
        $scope.form = form;
      });

      // Ensure the user is fully loaded.
      $rootScope.whenReady.then(function() {
        $scope.$on('formSubmission', function(event, submission) {

            // Set the logged in user's password.
            $rootScope.user.data.password = submission.data.password;

            // Now save the user back to the API.
            (new Formio(AppConfig.resources.employee.form)).saveSubmission($rootScope.user).then(function() {

                // Go to the home state after they reset their password.
                $state.go('home');
            });
        });
      });
    }]
  });

Now, the user can reset their own password and you can control every aspect of the workflow around it!

If you would like to see a working example of this whole process in action please download the following application.

Download Human Resources Application