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:
- Perl elementary knowledge required.
- Understanding of Mojolicious
- Familiarity with the DBIx.
- dbicdump must be installed on your system.
- Mojolicious, Perl and DBIx installed on your system.
- MySQL must be installed on your system.
- 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;
Once the commands execute successfully. It's time to add users to the tables. There are two ways to do it:
- Run the Insert command.
- 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:
- We need to register a route first to open the registration form.
- We need to create a Controller to accommodate the route.
- User registration template.
- We need to create a Controller to post the user registration data.
- 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' );
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;
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> > <%= content %> >
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>
$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') ); }
When you visit the browser and hit localhost:3000/register. You see the following screen.
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' );
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); }
And don't forgot to import the use Crypt::PBKDF2; in the RegistrationController.
Visit the URL  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 ); }
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; } }
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.
Discussion (7)
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?
Need to use JS to prevent the request send to backend. I believe in that case chrome will not offer to save password
I'd love to see that additional recipe for confirming an email with JWT :)
Sure, I'll write it :)
that would be lovely :)
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?
Yes, CSRF can also be used in the controller you need to write this code in the controller
And in the Template
That's it. You are good to with.