Code Happy: Authentication

← Back to Index

Please note that this chapter was written for VERSION 3 of the Laravel PHP Framework.

Many applications will want a layer of Authentication. If you are writing a blog, you don't want your readers to be able to post new topics. If you're working with some sensitive data, you don't want unauthorised users accessing it.

Fortunately, Laravel has a simple, secure, and highly customisable Authentication class. Let's take a look at how we can interact with it.

Setup

Before we begin you are going to need to create a new table to store our user details. We can name this table whatever we like, but if we name it users we won't have to change the Authentication's configuration file. Here's how to create a suitable table with the Schema Builder.

<?php

Schema::create('users', function($table) {
    $table->increments('id');
    $table->string('username', 128);
    $table->string('password', 64);
});

You can add as many additional fields as you like, but this will get us going. Let's also create a sample user that we can use to test the authentication process. First I should explain how the Hash class works.

You can use the Hash class to hash a password using the highly secure bcrypt algorithm. It's very simple to use, here is an example.

<?php

$pass = Hash::make('my_password_string');

In the above snippet we create a bcrypt hash out of our password. By storing the hash in the database instead of the plain text password, it offers our users some extra security. You will find this is common practice with web applications.

If you would like to compare a hashed password with a value, simply use the check() method. For example..

<?php

Hash::check('my_pass', $pass);

This will return a boolean result true on successful match, and false on failure. Now that we know how to hash a password, we can create our sample user. I am going to call him Dexter. You see I am watching the TV show Dexter while writing this chapter, it's great to write with background noise, try it with coding, it really works! Onwards to Dexter..

<?php

DB::table('users')->insert(array(
    'username'  => 'Dexter',
    'password'  => Hash::make('knife')
));

Now we must choose which of the default authentication drivers we wish to use. We have the choice of 'eloquent' or 'fluent'.

The 'fluent' driver will use Fluent to interact with the database, and return an object representing the the user tables row when we call Auth::user(). The eloquent driver will return an Eloquent model representing the user instead.

Configuration for authentication driver, table or object name, and field names can all be found within `application/config/auth.php'.

<?php

return array(

    'driver' => 'eloquent',

    'username' => 'email',

    'model' => 'User',

    'table' => 'users',
);

Let's change 'driver' to fluent to use the fluent query builder as the authentication driver, and change the 'username' config item to 'username' so that we can log our users into our application using their username rather than an email address.

<?php

return array(

    'driver' => 'fluent',

    'username' => 'username',

    'model' => 'User',

    'table' => 'users',
);

Setting up the form

Righto! We are going to need a login form, let's make a pretty one with blade! We love Blade now right? Let's do it. Creating login.blade.php..

{{ Form::open('login') }}

    <!-- check for login errors flash var -->
    @if (Session::has('login_errors'))
        <span class="error">Username or password incorrect.</span>
    @endif

    <!-- username field -->
    <p>{{ Form::label('username', 'Username') }}</p>
    <p>{{ Form::text('username') }}</p>

    <!-- password field -->
    <p>{{ Form::label('password', 'Password') }}</p>
    <p>{{ Form::password('password') }}</p>

    <!-- submit button -->
    <p>{{ Form::submit('Login') }}</p>

{{ Form::close() }}

That is a beautif..

Ssssh, that was a few chapters ago now, I'm not into brain washing, you can let it go. Sure is a beautiful form though! Let's create a nice route to show the form.

<?php

Route::get('login', function() {
    return View::make('login');
});

Let's make a POST variant of this route, we can use that to handle when the login form is submitted. That way we can use a single URL for both.

<?php

Route::post('login', function() {
    return 'login form sent';
});

Right let's make sure our routes work fine, actually I am pretty sure they will, but this is just a habit of mine. If we visit 'http://localhost/login' and enter some phoney data and submit the form we should get login form sent.

Handling Login

Note that before we can login, we will need to setup a session driver. This is because Laravel logins are stored in the session. If you head over to application/config/session.php it will list all your options, go ahead and choose a suitable one.

Great! Let's handle that login attempt and go to work on that post route. First let's get the input data that has been posted from the form.

<?php

Route::post('login', function() {

    // get POST data
    $userdata = array(
        'username'      => Input::get('username'),
        'password'      => Input::get('password')
    );

});

Sweet! We have post data, lets put it to use. We will use the Auth::attempt() method to check if the username and password can be used to login. The great thing about this method is on success it will automatically create our 'logged-in' session for us! Mucho conveniento! (I never took Spanish, sorry.).

<?php

Route::post('login', function() {

    // get POST data
    $userdata = array(
        'username'      => Input::get('username'),
        'password'      => Input::get('password')
    );

    if ( Auth::attempt($userdata) )
    {
        // we are now logged in, go to home
        return Redirect::to('home');
    }
    else
    {
        // auth failure! lets go back to the login
        return Redirect::to('login')
            ->with('login_errors', true);
        // pass any error notification you want
        // i like to do it this way :)
    }

});

If our authentication is successful, the login session is created and we are redirected to the home route. Perfect, but before we go any further let's create a logout route so that we can logout to perform future testing.

<?php

 Route::get('logout', function() {
    Auth::logout();
    return Redirect::to('login');
});

Here we use the Auth::logout() method to destroy the login session, and head back to the login page. We are now logged out.

Right, now that we have our login working perfectly let's create our super secret home page, add a logout link, and welcome our currently logged in user.

<?php

//---- THE ROUTE ----

Route::get('home', function() {
    return View::make('home');
});

//---- THE VIEW (home.blade.php) ----

<div class="header">
    Welcome back, {{ Auth::user()->username }}!<br />
    {{ HTML::link('logout', 'Logout') }}
</div>

<div class="content">
    <h1>Squirrel Info</h1>
    <p>This is our super red squirrel information page.</p>
    <p>Be careful, the grey squirrels are watching.</p>
</div>

Now you can test our login loop.. Login, be greeted with the welcome page, logout. Repeat. Do this at least 800 times to set your mind to rest. Don't worry, this is normal behaviour, besides.. you get paid by the hour right PHP Guru?

You will notice that we use Auth::user() in the above example, this will return a Fluent database result object representing the current logged in user. Very handy for finding its id, or echoing out welcome information.

Protecting Routes

Ok, we are almost done here! There is a slight bug, a security issue that we need to take care of first. Logout of the system, (no not the machine you are reading this on, just the site) and head over to the home URL by hand.

Uh oh, now the grey squirrels can see our attack plans without even logging in! We will need to fix this. Also because we don't have a user logged into the system, we get an undefined index (or something similar) error when trying to access the user's username.

Cast your mind way back, the solution is there lurking in the shadows near the start of the book. What? No shadows? I paid the publisher to put them in... never mind let's carry on. Do you remember route filters? Using filters we can run a snippet of code before the route is executed, and if we return something it will be used in place of what we return from our route. Woah, lots of potential there.

It's even easier than that, you see Taylor has a degree in amateur mind reading, he knew we would be writing this chapter, and he knew we would need to protect a route from non-logged in users. That is why he created the 'auth' filter, which is included with Laravel as standard. Let's have a look at the filter itself. (You can find this in routes.php)

<?php

Route::filter('auth', function()
{
    if (Auth::guest()) return Redirect::to('login');
});

Neat!

You see the Auth::guest() method? It's a nicely expressive method which returns true only if the current request has no logged in user. Very handy! You can also use Auth::check() to perform the opposite check, to see if a user is currently logged in. We know these methods do exactly the same thing, but by providing clean expressive method names, using the right one will appear much clearer within your source.

As you can see, if no user is logged in the auth filter returns a redirect to the login page, overwriting the view supplied by our route. All we need to do is attach this to our home route.

<?php

Route::get('home', array('before' => 'auth', 'do' => function() {
    return View::make('home');
}));

There we go, now the home route is protected, the undefined notices will never be seen, and unauthorised squirr... users will no longer be able to see the home page. Please remember not to apply the auth filter to your login URI, you will experience a terrible loop!

Customisation

I know what you're thinking. What if I don't want to use Eloquent or Fluent, this seems very limited!

Please, this is Laravel. You should have learned this by now! Laravel allows you to create custom classes known as 'Auth Drivers' so that you can modify parameters or hook into any authentication system you like. Simply create a new class. I like to put mine in application/libraries so that they are auto-loaded for me!

<?php

// application/libraries/myauth.php
class Myauth extends Laravel\Auth\Drivers\Driver {

    public function attempt($arguments = array())
    {

    }

    public function retrieve($id)
    {

    }

}

Your authentication driver must extend Laravel\Auth\Drivers\Driver and contain the two methods listed above. The first method accepts the array of username and password and is used to authenticate using your own method. On a successful authentication you should make a call to the login() method of the parent to inform Laravel that the authentication worked, for example..

<?php

public function attempt($arguments = array())
{
    $username = $arguments['username'];
    $password = $arguments['password'];

    $result = my_login_method($username, $password);

    if($result)
    {
        return $this->login($result->id, array_get($arguments, 'remember'));
    }

    return false;
}

The login method accepts an identifier (that can be used later to retrieve the user) and the value of the 'remember' key from our arguments array. On authentication failure the method should always return false.

The retrieve() method is handed the identifier that you previously passed to the login() method, you can use this to return an object that represents the current user. For example..

<?php

public function retrieve($id)
{
    return get_my_user_object($id);
}

Great! Now we have a working authentication driver. Before we can use it we will need to register it with the Auth class. Add the following code to your application/start.php.

<?php

Auth::extend('myauth', function() {
    return new Myauth();
});

Pass an identifier for your authentication driver as the first parameter to Auth::extend(), the second parameter is a Closure that is used to return a new instance of the class.

All that is left to do is update your application/config/auth.php file to point to this new authentication driver..

<?php

'driver' => 'myauth',

and now enjoy using your authentication system in the usual way!

My books are available online for free to encourage learning. However, if you'd like for me to keep writing, then please consider buying a digital copy over at Leanpub.com.

It's available in PDF, ePub, and Kindle format, and contains a bunch of extras that you won't find on the site. I have a full-time job, and I write my books in my spare time. Please consider buying a copy so that I can continue to write new books from the comfort of my sofa!