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.

Beteilige dich an der Unterhaltung

15 Kommentare

  1. 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

    1. I found response:

      use from this code in global.php:

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

  2. 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.

  3. 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

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

    Where do we put this class?

    1. 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.

  5. 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

  6. Hi,

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

    How can I implement Sha256 for password?

    Thanks,

  7. 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.

    1. 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.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.