Acme-CPANModulesBundle-Import-MojoliciousAdvent-2018
view release on metacpan or search on metacpan
devdata/https_mojolicious.io_blog_2018_12_01_welcome-mojoconf-recap_ view on Meta::CPAN
Well this time I hit the most embarrassing of them all.</p>
<p>I realize the problems inherent with live demos and so I do what I can to prevent them: I practice, over and over.
This time, gentle reader, I learned a new lesson:</p>
<blockquote>
<p>Practicing your live demo includes practicing logging in.
<cite>Joel Berger, today</cite></p>
</blockquote>
<p>That's right, I forgot the login credentials to my own demo.</p>
<p>That said, most of the talk still worked.
So beyond that first lesson, here's one more: even experienced speakers mess up, we shrug and move on.
Don't let fear of failure stop you from speaking to groups of like minded colleagues about the work you do.</p>
<p>The talk is about migrating from a Lite app to a full app.
If you find yourself feeling afraid or confused in moving to a full app, or if you read the Mojolicious documentation and wonder how it applies to a full app, give this a watch.
Bonus material about modern Javascript at the end too.</p>
<p><iframe allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen frameborder="0" height="480" src="https://www.youtube.com/embed/ycAXeOKLCGc" width="854"></iframe></p>
devdata/https_mojolicious.io_blog_2018_12_08_authenticating-with-ldap_ view on Meta::CPAN
The result is that this post is about authenticating a <strong>Full App</strong> and isn't as
svelte as the other posts talking about their Lite apps.</p>
<p>Jumping straight in, let's assume that you already have a Login page
in your templates and it has a form which posts data to <code>/login</code>.
If you've got a route like this</p>
<pre><code>$r->post('/login')->name('do_login')->to('Secure#on_user_login');
</code></pre>
<p>to send the credentials to your controller. Or if you're cool with
<a href="https://mojolicious.org/perldoc/Mojolicious/Guides/Routing#Named-routes">named routes</a>,
your template might include this line</p>
<pre><code><form action="<%= url_for 'do_login' %>" method="POST">
</code></pre>
<p>Pro tip: You can even simplify it to</p>
<pre><code>%= form_for 'do_login'
</code></pre>
devdata/https_mojolicious.io_blog_2018_12_08_authenticating-with-ldap_ view on Meta::CPAN
<pre><code>package MyApp::Controller::Secure;
use Mojo::Base 'Mojolicious::Controller';
sub on_user_login {
my $self = shift;
my $username = $self->param('username');
my $password = $self->param('password');
if (check_credentials($username, $password)) {
$self->render(text => 'Hello Bender!');
}
else {
$self->render(
text => '<h2>Login failed</h2><a href="/login">Try again</a>',
format => 'html',
status => 401
);
}
}
sub check_credentials {
my ($username, $password) = @_;
return $username eq 'Bender' && $password eq 'rocks';
}
</code></pre>
<h2>Storing passwords - MojoX::Auth::Simple</h2>
<p>We can agree that hard-coding usernames and passwords is not sustainable.
If you can connect to a database, any database that your Perl
<a href="https://metacpan.org/pod/DBI">DBI</a> module can connect to,
then you might think that
<a href="https://metacpan.org/pod/MojoX::Auth::Simple">MojoX::Auth::Simple</a>
will solve your problems. Further reading will tell you that it only
provides the helper methods <code>log_in</code>, <code>is_logged_in</code> and <code>log_out</code>
which are useful for everything around the authentication, but not the
authentication itself. But, since you're using a database now, you
could change the <code>check_credentials</code> to something better than this
(wot was cooked up on a Friday afternoon and not tested)</p>
<pre><code>sub check_credentials {
my ($username, $password) = @_;
my $statement = <<'SQL'; # NO! Don't do this!
SELECT username FROM user_passwd
WHERE username = ? AND password = ?
SQL
my $sth = $dbh->prepare($statement);
$sth->execute($username, $password) or return;
my @row = $sth->fetchrow_array();
devdata/https_mojolicious.io_blog_2018_12_08_authenticating-with-ldap_ view on Meta::CPAN
<pre><code> my $statement = <<'SQL'; # better
SELECT username FROM user_passwd
WHERE username = ? AND password = SHA2(?, 256)
SQL
</code></pre>
<p>or encrypt with Perl</p>
<pre><code>use Crypt::Digest::SHA256 qw/ sha256 /;
sub check_credentials {
my ($username, $password) = @_;
my $encrypted = sha256($password);
...
$sth->execute($username, $encrypted) or return;
</code></pre>
<p>Technically, AES is an encryption algorithm and SHA-2 is a hashing algorithm,
meaning that the transformation is effectively one-way and is more secure.
devdata/https_mojolicious.io_blog_2018_12_08_authenticating-with-ldap_ view on Meta::CPAN
and stored the <code>$hash</code> value in your database like this</p>
<pre><code>my $hash = password_hash($initial_password);
my $sth = $dbh->prepare('INSERT INTO user_passwd (username, password) VALUES (?, ?)');
$sth->do($username, $hash);
</code></pre>
<p>you should be ok to change the sub to</p>
<pre><code>sub check_credentials {
my ($username, $password) = @_;
my $statement = 'SELECT password FROM user_passwd WHERE username = ?';
my $sth = $dbh->prepare($statement);
$sth->execute($username) or return;
my ($encoded) = $sth->fetchrow_array();
$sth->finish();
return password_verify($password, $encoded);
}
</code></pre>
<p><a href="https://metacpan.org/pod/Mojolicious::Plugin::Scrypt">Mojolicious::Plugin::Scrypt</a>
will use the Scrypt algorithm,
but can also use Argon2 (which was recommended to me at LPW), Bcrypt and more.
So, assuming that you've stored your password with
<code>my $encoded = $app->scrypt($password);</code>
the <code>on_user_login</code> sub becomes</p>
<pre><code>sub check_credentials {
my ($username, $password) = @_;
my $statement = 'SELECT password FROM user_passwd WHERE username = ?';
my $sth = $dbh->prepare($statement);
$sth->execute($username) or return;
my ($encoded) = $sth->fetchrow_array();
$sth->finish();
# WAIT! where did $self come from
return $self->scrypt_verify($password, $encoded);
}
</code></pre>
<p>Oh, dear. The above crashes because of a design decision made early on in the writing process.
I invoked <code>check_credentials</code> as a plain sub, not the method of an object.
Using a Plugin depends on having the controller available, so the following changes are necessary.</p>
<pre><code>sub on_user_login {
my $self = shift;
...
if ($self->check_credentials($username, $password)) {
...
}
sub check_credentials {
my ($self, $username, $password) = @_;
...
return $self->scrypt_verify($password, $encoded);
}
</code></pre>
<p>Y'know, I'm sitting here on the Group W bench thinkin' ...
if I'm going to re-write this whole tutorial, maybe I should've started with
<a href="https://metacpan.org/pod/Mojolicious::Plugin::Authentication">Mojolicious::Plugin::Authentication</a>
and taken you through the code you needed for the <code>validate_user</code> option in the Plugin.
devdata/https_mojolicious.io_blog_2018_12_08_authenticating-with-ldap_ view on Meta::CPAN
use YAML qw/LoadFile/;
my $config_file = 'ldap_config.yml';
my $config = LoadFile($config_file);
my ($LDAP_server, $base_DN, $user_attr, $user_id, )
= @{$config}{ qw/server baseDN username id/ };
...
sub check_credentials {
my ($username, $password) = @_;
return unless $username;
my $ldap = Net::LDAP->new( $LDAP_server )
or warn("Couldn't connect to LDAP server $LDAP_server: $@"), return;
my $message = $ldap->bind( $base_DN );
my $search = $ldap->search( base => $base_DN,
filter => join('=', $user_attr, $username),
attrs => [$user_id],
( run in 0.270 second using v1.01-cache-2.11-cpan-a5abf4f5562 )