Write Your Own Auth-Driver for Laravel 4

Because of some requests I translated my post Ein eigener Auth-Driver in Laravel 4. Enjoy it!

Laravel 4 comes along with two authentication drivers: Database und Eloquent. It is perfect for databases with PDO-drivers. But if you want to authenticate a user against other databases like MongoDB or web services etc., you have to do some work. Implementing an own auth-drive in Laravel 4 is a little bit different than in Laravel 3, so I want to tell you how it works.

Configuration

At first we have to tell Laravel which auth-driver we want to use. We will do it in app/config/auth.php. There we will find a line

'driver' => 'eloquent',

Here we will declare our own auth-driver. In this post the driver is named example. Therefore, we modify the line as follows:

'driver' => 'example',

That is not enough. Laravel will comlain that is does not know an auth-driver called example. We have to declare how to use the driver example. We will do it app/start/global.php. Just add the following block:

Auth::extend('example', function($app) {
    $provider =  new \Example\Auth\ExampleUserProvider();

    return new \Illuminate\Auth\Guard($provider, $app['session']);
});

The UserProvider

Now we get to the core of the matter: We have to implement the class ExampleUserProvider. This is a possible implementation:

<?php

namespace Example\Auth;

use Illuminate\Auth\UserProviderInterface,
    Illuminate\Auth\GenericUser,
    Example\Domain\User\Service\UserService,
    Example\Domain\User\Entity\User;

class ExampleUserProvider
    implements UserProviderInterface
{
    /**
    * @var UserService
    */
    private $userService;

    public function __construct(UserService $userService)
    {
        $this->userService = $userService;
    } 

    /**
     * Retrieve a user by their unique identifier.
     *
     * @param  mixed  $identifier
     *
     * @return \Illuminate\Auth\UserInterface|null
     */
    public function retrieveByID($identifier)
    {
        /** @var User $user  */
        $user = $this->userService->findUserByUserIdentifier($identifier);

        if (!$user instanceof User) {
            return false;
        }

        return new GenericUser([
            'id'       => $user->getUserIdentifier(),
            'username' => $user->getUserName()
        ]);
    }

    /**
     * Retrieve a user by the given credentials.
     *
     * @param  array  $credentials
     *
     * @return \Illuminate\Auth\UserInterface|null
     */
    public function retrieveByCredentials(array $credentials)
    {
        /** @var User $user  */
        $user = $this->userService->findUserByUserName($credentials['username']);

        if (!$user instanceof User) {
            return false;
        }

        return new GenericUser([
            'id'       => $user->getUserIdentifier(),
            'username' => $user->getUserName()
        ]);
    }

    /**
     * Validate a user against the given credentials.
     *
     * @param \Illuminate\Auth\UserInterface $user
     * @param  array  $credentials
     *
     * @return bool
     */
     public function validateCredentials(\Illuminate\Auth\UserInterface $user, array $credentials)
     {
         $validated = $this->userService->validateUserCredentials(
             $credentials['username'],
             $credentials['password']
         );

         $validated = $validated && $user->userName = $credentials['username'];

         return $validated;
     }
}

In Laravel 4 a UserProvider has to implement the UserProviderInterface and with it the methods

  • retrieveByID($id),
  • retrieveByCredentials($credentials) und
  • validateCredentials($user, $credentials).

The User-Object

Auth::user() allows to access the logged in user. But int the session Laravel only stores the ID. If we want to access the other data of the logged in user, we have to retrieve it from the database or another source. This is the job of the method retrieveById($id).

retrieveById($id)

You have to pass an ID as parameter. This can be an integer, a string or any other type.

The method has to return an object implementing the interface UserInterface. In the simple case – like in this example – it is the GenericUser that comes along with Laravel. The returned object has to have the property id, because it is stored in the session.

Authentication

Auth::attempt($credentials) kicks off the authentication. In Laravel 4 the authentication is done in two steps. At first retrieveByCredentials($credentials) finds a user satisfying the credentials. In the second step validateCredentials($user, $credentials) checks whether the return object fits to the credentials.

It seems that there is Dwork done twice, but it makes sense. It is not always possible to find a user by username and password. In the example above we can only find a user by its username. In the DatabaseUserProvider, that comes along with Laravel 4, the user is found by its password. In both cases you have to check, whether the credentials fits to the user object. Furthermore it makes testing easier.

retrieveByCredentials($credentials)

You have to pass an array $credentials as parameter. Typically it contains the keys username und password. Basically it is the developers choice, which data are passed. It is just important that you get the object you want.

The method has to return an object implementing the interface UserInterface too. In the simplest case it is the GenericUser.

validateCredentials($user, $credentials)

You have to pass an object $user implementing UserInterface and an array $credentials as parameters.

True will be returned if $user and $credentials matches, otherweise false will be returned.

Conclusion

Implementing an own Auth-Drivers in Laravel 4 is no rocket science. Just implement a UserProvider with these three methods, declare it to thes ssystem and tell the system that you want to use it.

15 Kommentare zu “Write Your Own Auth-Driver for Laravel 4”

  1. jacopo 24. Juli 2013 at 12:20 #

    Really nice information. Appreciate it

  2. Majid 6. Oktober 2013 at 10:28 #

    hi.
    i test your approach but i get exception with this message:

    Argument 2 passed to Illuminate\Auth\Guard::__construct() must be an instance of Illuminate\Session\Store, instance of Illuminate\Session\SessionManager given, called in /Users/Shared/Code/Repo/Horn/RestfulService/app/start/global.php on line 91 and defined

    • Majid 6. Oktober 2013 at 10:33 #

      I found response:

      use from this code in global.php:

      return new \Illuminate\Auth\Guard($provider, App::make(’session.store‘));

      • scheddel 19. Mai 2014 at 11:09 #

        You are right. There was a change after I wrote this article.

  3. César 21. Juli 2014 at 07:20 #

    Nice article, was very helpful, the only question I have is: do you know how to use the Auth::extend function inside a custom ServiceProvider? Seems it’s not possible to use Facades in the ServiceProvider scope.

    • scheddel 23. Juli 2014 at 14:38 #

      Did you care about the namespace? The facade Auth is global, so you need to prefix it with \ when you want to use it within an namespace.

  4. Anthony 4. September 2014 at 19:18 #

    First of all I am very new to larvel and web programming, so please be nice to me 🙂

    In this line:

    $provider = new \Example\Auth\ExampleUserProvider();

    You do not need to pass an object to the constructor?

    It seems the constructor needs an object reference here:

    public function __construct(UserService $userService)
    {
    $this->userService = $userService;
    }

    My goal is to call the magento api functions within the $userservice. Any help would be appreciated thank you.

    Anthony

    • scheddel 15. September 2014 at 17:40 #

      You are right. You have to pass an object.

  5. Chris 13. Oktober 2014 at 07:10 #

    „Now we get to the core of the matter: We have to implement the class ExampleUserProvider“

    Where do we put this class?

    • scheddel 23. Oktober 2014 at 10:16 #

      It depends on the structure of your project. I have my business logic i an own library so I put it there.

      If you have a normal Laravel project, creating a directory app/components und putting it here might be an option.

  6. TAD 6. Januar 2015 at 17:08 #

    This is a nice tutorial but I get an error on it.

    Argument 1 passed to Illuminate\Auth\JentUserProvider::__construct() must be an instance of Illuminate\Hashing\HasherInterface, none given, called in /home/laravel/domains/laravel.local/app/start/global.php on line 15 and defined

    What could this be? i’ve tried to include „use the Illuminate\Hashing\HasherInterface“ on a lot of places that I had to change, but nothing works

  7. Sina 11. März 2015 at 04:01 #

    Hi,

    I work on Login and we want the password type as Sha256.

    How can I implement Sha256 for password?

    Thanks,

  8. Ashwin 15. April 2015 at 14:41 #

    Nice tutorial. I had a quick question.

    public function retrieveByID($identifier)
    {
    /** @var User $user */
    $user = $this->userService->findUserByUserIdentifier($identifier);

    if (!$user instanceof User) {
    return false;
    }

    return new GenericUser([
    ‚id‘ => $user->getUserIdentifier(),
    ‚username‘ => $user->getUserName()
    ]);
    }

    in the above code. If the user is not found it returns false. So on the other end, In the validateCredentials it expects for a UserInterface.

    In my case in throws an error.

    • scheddel 15. April 2015 at 17:55 #

      I never had the problem, because I used Auth::attempt() in my controllers. So let’s have a look at Auth::attempt():

      /**
      * Attempt to authenticate a user using the given credentials.
      *
      * @param array $credentials
      * @param bool $remember
      * @param bool $login
      * @return bool
      */
      public function attempt(array $credentials = array(), $remember = false, $login = true)
      {
      $this->fireAttemptEvent($credentials, $remember, $login);

      $this->lastAttempted = $user = $this->provider->retrieveByCredentials($credentials);

      // If an implementation of UserInterface was returned, we'll ask the provider
      // to validate the user against the given credentials, and if they are in
      // fact valid we'll log the users into the application and return true.
      if ($this->hasValidCredentials($user, $credentials))
      {
      if ($login) $this->login($user, $remember);

      return true;
      }

      return false;
      }

      The method retrieveByCredentials() is called first and this always returns an instance of GenericUser.

      But returning false is indeed not a good idea. Replace it by new GenericUser([]) and it should fit.

Trackbacks/Pingbacks

  1. php - Laravel 4 - Hardcoded authentication - php - 17. Januar 2016

    […] found out create own user driver, seems me there should […]

Schreib einen Kommentar!