DEV Community

Ashutosh
Ashutosh

Posted on

How to do the User Registration in Mojolicious

Hello Mojo Learners, in this article we learn, how to create a user registration process, in the Mojolicious Framework.

PreRequisites

Before proceeding, there are some prerequisites:

  1. Perl elementary knowledge required.
  2. Understanding of Mojolicious
  3. Familiarity with the DBIx.
  4. dbicdump must be installed on your system.
  5. Mojolicious, Perl and DBIx installed on your system.
  6. MySQL must be installed on your system.
  7. Familiarity with MySQL basic commands.

If you are not familiar with anyone of the above, then I recommend to get required elementary knowledge.

In my previous article, we covered how to setup and add the MySQL and DBIx::Class to our Mojo App. Please visit here to set up the MySQL and DBIx;

We already have the MySQL setup in our previous article. To register the user, we must create the user table first.

CREATE TABLE myApp_database.user (
  `id` bigint NOT NULL,
  `status` tinyint DEFAULT '1',
  `email` varchar(120) NOT NULL,
  `password` varchar(135) DEFAULT NULL,
  `first_name` varchar(80) DEFAULT NULL,
  `middle_name` varchar(80) DEFAULT NULL,
  `last_name` varchar(80) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB;
Enter fullscreen mode Exit fullscreen mode

Once the commands execute successfully. It's time to add users to the tables. There are two ways to do it:

  1. Run the Insert command.
  2. Create a user registration in Mojolicious.

We follow the later and create a user registration process in the Mojo framework.

Let's think about the steps that we need to follow to create the user registration process:

  1. We need to register a route first to open the registration form.
  2. We need to create a Controller to accommodate the route.
  3. User registration template.
  4. We need to create a Controller to post the user registration data.
  5. Ways to encrypt the password.

To start with, I think we are ok.

Start with the first point and create a new route to open the registration form.

Go to the MyApp.pm and in the startup subroutine register the route.

$r->get('/register')->to(
    controller => 'RegistrationController', action => 'register'
);
Enter fullscreen mode Exit fullscreen mode

It says, when a user hits/register route, it goes to the RegistrationController and looks out for the register subroutine. The problem is we don't have the RegistrationController yet. Create it under the Controller folder.

package MyApp::Controller::RegistrationController;
use Mojo::Base 'Mojolicious::Controller';

sub register {
    my $c = shift;
    $c->render(template => 'register')
}

1;
Enter fullscreen mode Exit fullscreen mode

In the register subroutine, $c is the default variable and we render the template register under the templates folder. Repeatedly we have not created the register template yet. Before creating 'register.html.ep' file go to the default.html.ep under the layouts and add bootstrap 4 files in it.

<!DOCTYPE html>
<html>

<head>
    <title><%= title %></title>

    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous" />

</head>

<body>
    <%= content %>

    <script src="https://code.jquery.com/jquery-3.4.1.slim.min.js"
        integrity="sha384-J6qa4849blE2+poT4WnyKhv5vZF5SrPo0iEjwBvKU7imGFAV0wwj1yYfoRSJoZ+n"
        crossorigin="anonymous">
    </script>

    <script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js"
        integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo"
        crossorigin="anonymous">
    </script>

    <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js"
        integrity="sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6"
        crossorigin="anonymous">
    </script>
</body>

</html>
Enter fullscreen mode Exit fullscreen mode

Now create the register.html.ep file.

% layout 'default';
<br /> <br />
<div class="container">
    <div class="card col-sm-6 mx-auto">
        <div class="card-header text-center">
            User Registration Form
        </div>
        <br /> <br />
        <form method="post" action='/register'>
            <input class="form-control" 
                   id="username" 
                   name="username" 
                   type="email" size="40"
                   placeholder="Enter Username" 
             />
            <br /> <br />
            <input class="form-control" 
                   id="password" 
                   name="password" 
                   type="password" 
                   size="40" 
                   placeholder="Enter Password" 
             />   
            <br /> <br />
            <input class="form-control" 
                   id="confirm_password" 
                   name="confirm_password" 
                   type="password" 
                   size="40" 
                   placeholder="Confirm Password" 
             />   
            <br /> <br />
            <input class="form-control" 
                   id="first_name" 
                   name="firstName" 
                   type="password" 
                   size="40" 
                   placeholder="User's First Name" 
             />   
            <br /> <br />
            <input class="form-control" 
                   id="middle_name" 
                   name="middleName" 
                   type="password" 
                   size="40" 
                   placeholder="User's Middle Name" 
             />   
            <br /> <br />
            <input class="form-control" 
                   id="username" 
                   name="lastName" 
                   type="password" 
                   size="40" 
                   placeholder="User's Last Name" 
             />   
            <br /> <br />
            <input class="btn btn-primary" type="submit" value="Register">
            <br />  <br />
        </form>
      % if ($error) {
            <div class="error" style="color: red">
                <small> <%= $error %> </small>
            </div>
        %}

        % if ($message) {
            <div class="error" style="color: green">
                <small> <%= $message %> </small>
            </div>
        %}
    </div>

</div>
Enter fullscreen mode Exit fullscreen mode

$error and $message are the variables that we will pass to the register template. Modify the register subroutine and add the following:

sub register {
    my $c = shift;

    $c->render(
        template => 'register',
        error    => $c->flash('error'),
        message  => $c->flash('message')
    );
}
Enter fullscreen mode Exit fullscreen mode

When you visit the browser and hit localhost:3000/register. You see the following screen.

User Registration Form

We are doing good as of now.

What we have done so far is to show the user registration page. Now we need to create a post route to submit the form.
Go to the MyApp.pm file and add the following under the startup subroutine.

$r->post('/register')->to(
        controller => 'RegistrationController',
        action     => 'user_registration'
);
Enter fullscreen mode Exit fullscreen mode

In the RegistrationController, add the subroutine user_registration

sub user_registration {
    my $c = shift;

    my $email = $c->param('username');
    my $password = $c->param('password');
    my $confirm_password = $c->param('confirm_password');
    my $first_name = $c->param('firstName');
    my $middle_name = $c->param('middleName');
    my $last_name = $c->param('lastName');

    if (! $email || ! $password || ! $confirm_password || ! $first_name || ! $last_name) {
        $c->flash( error => 'Username, Password, First Name and Last Name are the mandatory fields.');
        $c->redirect_to('register');
    }

    if ($password ne $confirm_password) {
        $c->flash( error => 'Password and Confirm Password must be same.');
        $c->redirect_to('register');
    }

    my $dbh = $c->app->{_dbh};

    my $user = $dbh->resultset('User')->search({ email => $email });

    if (! $user->first ) {
        eval {
            $dbh->resultset('User')->create({ 
                email       => $email,
                password    => generate_password($password),
                first_name  => $first_name,
                middle_name => $middle_name,
                last_name   => $last_name,
                status      => 1
            });
        };
        if ($@) {
            $c->flash( error => 'Error in db query. Please check mysql logs.');
            $c->redirect_to('register');
        }
        else {
            $c->flash( message => 'User added to the database successfully.');
            $c->redirect_to('register');
        }
    }
    else {
        $c->flash( error => 'Username already exists.');
        $c->redirect_to('register');
    }
}

sub generate_password {
    my $password = shift;

    my $pbkdf2 = Crypt::PBKDF2->new(
        hash_class => 'HMACSHA1', 
        iterations => 1000,       
        output_len => 20,         
        salt_len   => 4,         
    );

    return $pbkdf2->generate($password);
}
Enter fullscreen mode Exit fullscreen mode

And don't forgot to import the use Crypt::PBKDF2; in the RegistrationController.

Visit the URL http://localhost:3000/register and enter the required details and hit the register button and in the button of the screen you will see the message 'User added to the database succesfully'.

In this article, we learn how to register the user in the Mojolicious App. Offcourse it is not production ready and we need to add some more validation like Validate Email, Password length from the user. Apart from it, we should make sure that User's First Name and Last Name shouldn't take any invalid characters like "@%$#^&*".

Here you can do something like this to validate an email. This should make sure that user enters the valid email address. Not only this, it should also check whether the extension like foo.com is valid exchanger or not. You can use this to integrate this in your app.

sub validate_email {
    my ($self, $email) = @_;

    return (
        Email::Valid->address(  
            -address => $email,  -mxcheck => 1 
        ) ? 1 : 0
    );
}
Enter fullscreen mode Exit fullscreen mode

And to validate the user's First Name, Middle and Last Name:

sub validate_name {
    my ($self,  $name) = @_;

    if ($name =~ /^[a-zA-Z]+$/ ) {
        return 1;
    }
    else {  
        return 0;
    }
}

Enter fullscreen mode Exit fullscreen mode

You can use any method to validate just to keep spammers out of your way to input the junk into your database.

We can also use JWT, to confirm the user by sending the encrypted text via email and when user clicks it, then we confirm the user. It will be covered in a separate article someday.

Till then good bye and love Mojolicious.

Top comments (7)

Collapse
 
bkerin profile image
bkerin

I'm liking this so far. One irritation that has come up is, when the password don't match chrome still offers to save one (I don't know which it picks :). Is there some way to prevent it from doing that?

Collapse
 
akuks profile image
Ashutosh

Need to use JS to prevent the request send to backend. I believe in that case chrome will not offer to save password

Collapse
 
bkerin profile image
bkerin

I'd love to see that additional recipe for confirming an email with JWT :)

Collapse
 
akuks profile image
Ashutosh

Sure, I'll write it :)

Collapse
 
bkerin profile image
bkerin

that would be lovely :)

Collapse
 
cam profile image
Cam Stuart • Edited

Thanks for this! it is very helpful!! Should a csrf token also be used in the form? if so, how do we verify it in the controller action?

Collapse
 
akuks profile image
Ashutosh • Edited

Yes, CSRF can also be used in the controller you need to write this code in the controller

# CSRF Protection
    my $v        = $c->validation;
    return $c->render( template => 'not_found.html.ep', status => 403 )
      if $v->csrf_protect->has_error('csrf_token');
Enter fullscreen mode Exit fullscreen mode

And in the Template

%= form_for '/form_action_url => ( method => 'post', name => 'form_name' ) => begin
    <!-- Other Form Fields -->
    %= csrf_field
% end
Enter fullscreen mode Exit fullscreen mode

That's it. You are good to with.