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>

            </div>

              <div class="post-thumb">
                <!-- theme suggests 1300x500 -->
                <img alt="Who goes there?" src="/blog/2018/12/08/authenticating-with-ldap/banner.jpg">
              </div>

            <div class="post-content">

              <section id="section-1">
                  <p>There are still quite a few people using LDAP in production,
but for those who are new to it,
LDAP is a directory with a tree-structure that&#39;s optimised for very fast lookups.
It used to be very common as a centralised authentication system
and if you&#39;re using Active Directory, you&#39;re using LDAP (mostly).
I wander through the wilderness of authentication,
ending with my solution on how to add LDAP authentication to your App.</p>

              </section>
              <section id="section-2">
                  <p>This post is based on a
<a href="https://docs.google.com/presentation/d/14ZbARlTj_3mxEf_9Bvbrz0AUYjQdFjIXfYuttb_J7uU">talk</a>
I gave at
<a href="https://act.yapc.eu/lpw2018">London Perl Workshop</a>
in 2018.
It&#39;s a little optimistic thinking that they&#39;ll get through
editing all the videos before Christmas, but we could hope
for an epiphany.
LDAP is just a small part of the authentication cycle here, so
this post generalises fairly well for those cases where you have
to write your own credential checker.
Oh, and the talk and writing this post has done exactly what was intended
and raised issues that I hadn&#39;t considered which I&#39;ve included here.
As a result, it&#39;s starting to read like
<a href="https://en.wikipedia.org/wiki/Alice%27s_Restaurant_Massacree">Alice&#39;s Restaurant</a>
without the full orchestration and five part harmony.
I hope this cautionary tale helps you to avoid the same pitfalls that I fell into.</p>

<p>In the meantime, have a
<a href="https://www.youtube.com/watch?v=t-BEo467pUI">Lightning Talk</a>
from MojoConf 2018.</p>

<h3>Route - lib/MyApp.pm</h3>

<p>First off, a confession.  I never really got into Lite Apps.
I know it&#39;s <a href="https://www.youtube.com/watch?v=ycAXeOKLCGc">easy</a>
to <a href="https://mojolicious.org/perldoc/Mojolicious/Guides/Growing">grow them into Full Apps</a>,
but I was under pressure to crank out a solution when I started and never got back to it.
The result is that this post is about authenticating a <strong>Full App</strong> and isn&#39;t as
svelte as the other posts talking about their Lite apps.</p>

<p>Jumping straight in, let&#39;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&#39;ve got a route like this</p>

<pre><code>$r-&gt;post(&#39;/login&#39;)-&gt;name(&#39;do_login&#39;)-&gt;to(&#39;Secure#on_user_login&#39;);
</code></pre>

<p>to send the credentials to your controller. Or if you&#39;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>&lt;form action=&quot;&lt;%= url_for &#39;do_login&#39; %&gt;&quot; method=&quot;POST&quot;&gt;
</code></pre>

<p>Pro tip: You can even simplify it to</p>

<pre><code>%= form_for &#39;do_login&#39;
</code></pre>

<p>which does it all for you including the <code>method</code> if the route only handles <code>POST</code>.</p>

<h3>Controller - lib/MyApp/Controller/Secure.pm</h3>

<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/;
use YAML qw/LoadFile/;

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
server:   ldap.example.com
baseDN:   dc=users,dc=example,dc=com
username:   userid
id:     dn
</code></pre>

<p>where the values on the right match the attributes in your LDAP schema.</p>

<p>Just making the logic clear in the last step, I&#39;ve imported a constant
from Net::LDAP called <code>LDAP_INVALID_CREDENTIALS</code> and I use that to check
against the result from the server.</p>

<pre><code>use Net::LDAP qw/LDAP_INVALID_CREDENTIALS/;

...

  return ($login-&gt;code == LDAP_INVALID_CREDENTIALS) ? 0 : 1;
}
</code></pre>

<p>The logic is a little back to front with the ternary operator, but
if the code I get from the server is <code>LDAP_INVALID_CREDENTIALS</code>
then I return <code>0</code>, a fail, otherwise I return <code>1</code>,
which is a true value for the <code>if</code> in the body of the <code>on_user_login</code>
function.</p>

<p>Yes, you&#39;re right once again.  I probably should be using
<a href="https://metacpan.org/pod/Mojolicious::Plugin::Config">Mojolicious::Plugin::Config</a>
to handle config files.  It&#39;s on my TODO list.</p>

<h2>Sessions</h2>

<p>Want more power managing sessions?  Well then, you want
<a href="https://metacpan.org/pod/MojoX::Session">MojoX::Session</a>

 view all matches for this distribution
 view release on metacpan -  search on metacpan

( run in 0.406 second using v1.00-cache-2.02-grep-82fe00e-cpan-2c419f77a38b )