Acme-CPANModulesBundle-Import-MojoliciousAdvent-2018

 view release on metacpan or  search on metacpan

devdata/https_mojolicious.io_blog_2018_12_08_authenticating-with-ldap_  view on Meta::CPAN

<p>Let&#39;s get started by cribbing from the
<a href="https://mojolicious.org/perldoc/Mojolicious/Guides/Cookbook#Basic-authentication1">Mojolicious Cookbook</a>.</p>

<pre><code>package MyApp::Controller::Secure;
use Mojo::Base &#39;Mojolicious::Controller&#39;;

sub on_user_login {
  my $self = shift;

  my $username = $self-&gt;param(&#39;username&#39;);
  my $password = $self-&gt;param(&#39;password&#39;);

  if (check_credentials($username, $password)) {
    $self-&gt;render(text =&gt; &#39;Hello Bender!&#39;);
  }
  else {
    $self-&gt;render(
        text =&gt; &#39;&lt;h2&gt;Login failed&lt;/h2&gt;&lt;a href=&quot;/login&quot;&gt;Try again&lt;/a&gt;&#39;,
        format =&gt; &#39;html&#39;,
        status =&gt; 401
    );
  }
}

sub check_credentials {
  my ($username, $password) = @_;

  return  $username eq &#39;Bender&#39; &amp;&amp; $password eq &#39;rocks&#39;;
}
</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&#39;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 = &lt;&lt;&#39;SQL&#39;;      # NO! Don&#39;t do this!
SELECT username FROM user_passwd
WHERE username = ? AND password = ?
SQL

  my $sth = $dbh-&gt;prepare($statement);
  $sth-&gt;execute($username, $password) or return;
  my @row = $sth-&gt;fetchrow_array();
  $sth-&gt;finish();

  return $username eq $row[0];
}
</code></pre>

<p>with the database connection and handle <code>$dbh</code> left as an exercise to the reader.
And yes, you should prepare the SQL outside of the sub.
The <code>?</code> in the SQL statement are bind parameters, placeholders that make the database call faster and safer.</p>

<h4>Did you spot the HUGE mistake I made?</h4>

<p>Never, never, NEVER store passwords in plain text!  (Blame it on Friday afternoon)
You should encrypt the password before storing it with an algorithm like AES or SHA-2.
So, how about this for a better untested example? You can encrypt with SQL</p>

<pre><code>  my $statement = &lt;&lt;&#39;SQL&#39;;      # 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-&gt;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.
Here are a couple of modules that make it easier and safer:</p>

<p>A nice module out there for handling passwords is, well,
<a href="https://metacpan.org/pod/Passwords">Passwords</a>.
It&#39;s just a wrapper around some other modules that gives you a simple API
and will use <a href="https://metacpan.org/pod/Crypt::Eksblowfish::Bcrypt">Bcrypt</a> by default.
So if you&#39;ve hashed your password with the <code>password_hash</code> function
and stored the <code>$hash</code> value in your database like this</p>

<pre><code>my $hash = password_hash($initial_password);

my $sth = $dbh-&gt;prepare(&#39;INSERT INTO user_passwd (username, password) VALUES (?, ?)&#39;);
$sth-&gt;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 = &#39;SELECT password FROM user_passwd WHERE username = ?&#39;;

  my $sth = $dbh-&gt;prepare($statement);
  $sth-&gt;execute($username) or return;
  my ($encoded) = $sth-&gt;fetchrow_array();
  $sth-&gt;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&#39;ve stored your password with
<code>my $encoded = $app-&gt;scrypt($password);</code>
the <code>on_user_login</code> sub becomes</p>

<pre><code>sub check_credentials {
  my ($username, $password) = @_;

  my $statement = &#39;SELECT password FROM user_passwd WHERE username = ?&#39;;

  my $sth = $dbh-&gt;prepare($statement);
  $sth-&gt;execute($username) or return;
  my ($encoded) = $sth-&gt;fetchrow_array();
  $sth-&gt;finish();

  # WAIT! where did $self come from
  return $self-&gt;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-&gt;check_credentials($username, $password)) {
...
}

sub check_credentials {
  my ($self, $username, $password) = @_;
...
  return $self-&gt;scrypt_verify($password, $encoded);
}
</code></pre>

<p>Y&#39;know, I&#39;m sitting here on the Group W bench thinkin&#39; ...
if I&#39;m going to re-write this whole tutorial, maybe I should&#39;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.
But let&#39;s leave that for next year.</p>

<p>Further reading on storing passwords:</p>

<ul>
<li><a href="https://crackstation.net/hashing-security.htm#properhashing">Secure Salted Password Hashing</a> by Defuse Security.</li>
</ul>

<h2>How to <a href="https://metacpan.org/pod/Net::LDAP">LDAP</a></h2>

<p><em>remember LDAP?  ... this is a post about LDAP</em></p>

<p>These are the steps to authenticating:</p>

<ol>
<li>Connect to the LDAP server</li>
<li><strong>Bind</strong> to the server</li>
<li>Search for the user&#39;s unique identifier in LDAP</li>
<li><strong>Bind</strong> as the user with their password</li>
<li>Check the result code from the server</li>
</ol>

<p>First, you need to make a network connection to the LDAP server.
Next, you <a href="https://metacpan.org/pod/Net::LDAP#METHODS">bind</a> to the server.
&quot;Bind&quot; is the term used in LDAP for connecting to a particular location
in the LDAP tree.
The LDAP server has a setting on whether it allows binding anonymously
and determines whether you can search directory without a password
as I&#39;ve done in the example.
Then you search LDAP for the user (because the identifiers are <em>loooong</em>)
and then you bind as the user with the password they&#39;ve provided.
If this connection as the user with their password succeeds,
then you must have used the correct password.
LDAP hands you back a result from the <code>bind</code> as a
<a href="https://metacpan.org/pod/distribution/perl-ldap/lib/Net/LDAP/Message.pod">Net::LDAP::Message</a>
object on either success or failure,
so check the Message <code>code</code> to find out whether you should authenticate the user.</p>

<p>Here&#39;s the code</p>

<pre><code>package MyApp::Controller::Secure;
use Mojo::Base &#39;Mojolicious::Controller&#39;;
use Net::LDAP qw/LDAP_INVALID_CREDENTIALS/;

devdata/https_mojolicious.io_blog_2018_12_08_authenticating-with-ldap_  view on Meta::CPAN


my $config_file = &#39;ldap_config.yml&#39;;
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-&gt;new( $LDAP_server )
        or warn(&quot;Couldn&#39;t connect to LDAP server $LDAP_server: $@&quot;), return;
  my $message = $ldap-&gt;bind( $base_DN );

  my $search = $ldap-&gt;search( base =&gt; $base_DN,
                              filter =&gt; join(&#39;=&#39;, $user_attr, $username),
                              attrs =&gt; [$user_id],
                            );
  my $user_id = $search-&gt;pop_entry();
  return unless $user_id;                     # does this user exist in LDAP?

  # this is where we check the password
  my $login = $ldap-&gt;bind( $user_id, password =&gt; $password );

  # return 1 on success, 0 on failure with the ternary operator
  return $login-&gt;code == LDAP_INVALID_CREDENTIALS ? 0
                                                  : 1;
}
</code></pre>

<p>where you have a file <code>ldap_config.yml</code> in the top-level directory that looks a little like</p>

<pre><code># config values for connecting to LDAP



( run in 1.001 second using v1.01-cache-2.11-cpan-49f99fa48dc )