User authentication system implementation details

In the last section, we introduced the basic knowledge of Laravel Auth system and said what its core components are. In this section, we will focus on the implementation details of Laravel Auth system. Auth, also known as AuthManager, loads the Guard and UserProvider for authentication, as well as the default user registration and login implementation details. By combing through these implementation details, we can also know how to customize Auth authentication to meet the needs of user authentication in our own projects.

The guard and user provider are loaded through AuthManager

AuthManager load watchman and user provider to use a lot of methods, with text description is not very clear, we annotate the process used in the method to see the specific implementation details.

namespace Illuminate\Auth; Class AuthManager implements FactoryContract {/** * Attempts from$guardsProperty to get the specified Guard * * @param string$name
     * @return \Illuminate\Contracts\Auth\Guard|\Illuminate\Contracts\Auth\StatefulGuard
     */
    public function guard($name = null)
    {
        $name = $name? :$this->getDefaultDriver();

        return isset($this->guards[$name])?$this->guards[$name]
                    : $this->guards[$name] = $this->resolve($name); } /** * parses the Guard ** @param string for the given name$name
     * @return \Illuminate\Contracts\Auth\Guard|\Illuminate\Contracts\Auth\StatefulGuard
     *
     * @throws \InvalidArgumentException
     */
    protected function resolve($name) {// Get the Guard configuration //$config = ['driver'= >'session'.'provider'= >'users']
        $config = $this->getConfig($name);

        if (is_null($config)) {
            throw new InvalidArgumentException("Auth guard [{$name}] is not defined."); } // If the guard driver is defined by the extend method, call the custom Guard driver hereif (isset($this->customCreators[$config['driver']]) {return $this->callCustomCreator($name.$config); } //Laravel auth is configured to createSessionDriver by default$driverMethod = 'create'.ucfirst($config['driver']).'Driver';

        if (method_exists($this.$driverMethod)) {
            return $this- > {$driverMethod} ($name.$config);
        }

        throw new InvalidArgumentException("Auth guard driver [{$name}] is not defined."); } /** * Get the configuration of Guard with the given name from config/auth.php ** @param string$name
     * @return array
     */
    'guards'= > ['web'= > ['driver'= >'session'.'provider'= >'users',].'api'= > ['driver'= >'token'.'provider'= >'users',
        ],
    ],
    protected function getConfig($name{/ /'guards'= > [/ /'web'= > [/ /'driver'= >'session', / /'provider'= >'users',
        //    ],

        //    'api'= > [/ /'driver'= >'token', / /'provider'= >'users', //], //], // According to Laravel's default auth configuration, this method will get the key"web"Corresponding arrayreturn $this->app['config'] ["auth.guards.{$name}"]; } /** * calls the custom Guard driver ** @param string$name
     * @param  array  $config
     * @return mixed
     */
    protected function callCustomCreator($name, array $config)
    {
        return $this->customCreators[$config['driver']] ($this->app, $name.$config); } /** * Register a custom closure of the Guard driver into the customCreators property ** @param string$driver
     * @param  \Closure  $callback
     * @return $this
     */
    public function extend($driver, Closure $callback)
    {
        $this->customCreators[$driver] = $callback;

        return $this; } /** * Register a custom user provider creator into the customProviderCreators property ** @param String$name
     * @param  \Closure  $callback
     * @return $this
     */
    public function provider($name, Closure $callback)
    {
        $this->customProviderCreators[$name] = $callback;

        return $this; } /** * Create session-based authentication guard ** @param string$name
     * @param  array  $config
     * @return \Illuminate\Auth\SessionGuard
     */
    public function createSessionDriver($name.$config{/ /$config['provider'] = ='users'
        $provider = $this->createUserProvider($config['provider']???? null);$guard = new SessionGuard($name.$provider.$this->app['session.store']);

        if (method_exists($guard.'setCookieJar')) {
            $guard->setCookieJar($this->app['cookie']);
        }

        if (method_exists($guard.'setDispatcher')) {
            $guard->setDispatcher($this->app['events']);
        }

        if (method_exists($guard.'setRequest')) {
            $guard->setRequest($this->app->refresh('request'.$guard.'setRequest'));
        }

        return $guard; } // Create the user provider object public on which the Guard driver dependsfunction createUserProvider($provider = null)
    {
        if (is_null($config = $this->getProviderConfiguration($provider))) {
            return; } // If a custom user provider creator closure is registered through the Auth::provider method, call the closure to get the user provider objectif (isset($this->customProviderCreators[$driver = ($config['driver']???? null)])) {return call_user_func(
                $this->customProviderCreators[$driver].$this->app, $config
            );
        }

        switch ($driver) {
            case 'database':
                return $this->createDatabaseProvider($config);
            case 'eloquent'The default auth configuration returns the EloquentUserProvider object, which implements the Illuminate\Contracts\ auth interfacereturn $this->createEloquentProvider($config);
            default:
                throw new InvalidArgumentException(
                    "Authentication user provider [{$driver}] is not defined."); }} /** * will dynamically call the user authentication methods of the AuthManager agent's Guard via __call * by default, __call will call the SessionGuard method * @param String$method
     * @param  array  $parameters
     * @return mixed
     */
    public function __call($method.$parameters)
    {
        return $this->guard()->{$method} (...$parameters); }}Copy the code

Registered users

The default registered routes in the Laravel Auth system are as follows:

$this->post('register'.'Auth\RegisterController@register');
Copy the code

So the logic for registering a user is done by the Register method of the RegisterController

Class RegisterController extends Controller {// The method is defined as public in Illuminate\Foundation\Auth\RegisterUsersfunction register(Request $request)
    {
        $this->validator($request->all())->validate();

        event(new Registered($user = $this->create($request->all())));

        $this->guard()->login($user);

        return $this->registered($request.$user)
        
     }
     
    protected function validator(array $data)
    {
        return Validator::make($data['name'= >'required|string|max:255'.'email'= >'required|string|email|max:255|unique:users'.'password'= >'required|string|min:6|confirmed',]); } protectedfunction create(array $data)
    {
        return User::create([
            'name'= >$data['name'].'email'= >$data['email'].'password' => bcrypt($data['password']),]); }}Copy the code

The process of register is very simple, which is to verify the data entered by the user and write the data into the database to generate the user. The password encryption adopts bcrypt algorithm. If you need to change the password encryption method to salt and plain password hashing, you can change this part of logic in create method. After the user is registered, the SessionGuard login method is called to load user data into the application. Note that the login method does not authenticate the user, but simply loads the authenticated user into the application so that we can retrieve user data from Auth::user() anywhere in the application.

User Login Authentication

The Laravel Auth system login route is as follows

$this->post('login'.'Auth\LoginController@login');
Copy the code

Let’s look at the login logic in LoginController

Class LoginController extends Controller {/** * processes login requests */ publicfunction login(Request $request) {// Validate the login field$this->validateLogin($request); // Prevent malicious multiple login attemptsif ($this->hasTooManyLoginAttempts($request)) {
            $this->fireLockoutEvent($request);

            return $this->sendLockoutResponse($request); } // Perform login authenticationif ($this->attemptLogin($request)) {
            return $this->sendLoginResponse($request);
        }

        $this->incrementLoginAttempts($request);

        return $this->sendFailedLoginResponse($request); } // Try logging in to authenticate protectedfunction attemptLogin(Request $request)
    {
        return $this->guard()->attempt(
            $this->credentials($request), $request->filled('remember')); } // Get the value of the login field protectedfunction credentials(Request $request)
    {
        return $request->only($this->username(), 'password'); }}Copy the code

Auth:: Attempt () Auth:: Attempt () Auth:: Attempt ()

class SessionGuard implements StatefulGuard, SupportsBasicAuth
{
    public function attempt(array $credentials= [].$remember = false)
    {
        $this->fireAttemptEvent($credentials.$remember);

        $this->lastAttempted = $user = $this->provider->retrieveByCredentials($credentials); // If the login authentication succeeds, use the login method to load the user object into the applicationif ($this->hasValidCredentials($user.$credentials)) {
            $this->login($user.$remember);

            return true; } // If the login fails, an event can be triggered to notify the user of a suspicious login attempt.$this->fireFailedEvent($user.$credentials);

        return false;
    }
    
    protected function hasValidCredentials($user.$credentials)
    {
        return ! is_null($user) && $this->provider->validateCredentials($user.$credentials); }}Copy the code

The Attempt method of SessionGuard first retrieves user data from the user table using the user name through the user provider’s retriveBycredentials method, authenticating user information is done by using the user provider’s validateCredentials, The implementation classes of all the user providers implement the methods defined in the UserProvider interface contract, so the default one is EloquentUserProvider

Class EloquentUserProvider implements UserProvider {Pull the user instance public from the databasefunction retrieveByCredentials(array $credentials)
    {
        if (empty($credentials) ||
           (count($credentials) === 1 &&
            array_key_exists('password'.$credentials))) {
            return;
        }

        $query = $this->createModel()->newQuery();

        foreach ($credentials as $key= >$value) {
            if (! Str::contains($key.'password')) {
                $query->where($key.$value); }}return $query->first(); } // Validate user public with given user authentication datafunction validateCredentials(UserContract $user, array $credentials)
    {
        $plain = $credentials['password'];

        return $this->hasher->check($plain.$user->getAuthPassword()); }} class BcryptHasher implements HasherContract {// Calculate the hash value of the given value by the bcrypt algorithm publicfunction make($value, array $options = [])
    {
        $hash = password_hash($value, PASSWORD_BCRYPT, [
            'cost'= >$this->cost($options),]);if ($hash= = =false) {
            throw new RuntimeException('Bcrypt hashing not supported.');
        }

        return $hash; } // Verify that the hash value is given the plaintext value public computed by the bcrypt algorithmfunction check($value.$hashedValue, array $options = [])
    {
        if (strlen($hashedValue) = = = 0) {return false;
        }

        return password_verify($value.$hashedValue); }}Copy the code

The passwords are verified through the Hasher hash that the EloquentUserProvider relies on. The Laravel authentication system uses the Bcrypt algorithm to encrypt the plaintext passwords provided by the user and store them in the user table. The check method of the Haser hash verifies that the plaintext password is the original value of the stored ciphertext password using the password_verify method built into PHP.

After the main details of the user authentication system are sorted out, we know how to define our own Guard or UserProvider. First, they must implement the methods in their respective contracts to seamlessly integrate into Laravel Auth system. Then you need to register your Guard or Provider instance closures into Laravel by using Auth::extend and Auth:: Provider methods. The Guard and UserProvider customization doesn’t have to come together. You can customize the Guard alone and use the default EloquentUserProvider, or use the default SessionGuard with the custom UserProvider.

In the next section, I will give an example from our previous project development to better illustrate how to extend the Laravel Auth system.

This article has been included in a series of articles Laravel source learning, welcome to visit to read.