SoFunction
Updated on 2025-04-07

Detailed explanation of authenticating users by modifying Laravel Auth

Preface

This article mainly introduces the relevant content of using salt and password to authenticate users by modifying Laravel Auth. We will share it for your reference and learning. I won’t say much below. Let’s take a look at the detailed introduction together:

Laraval's own user authentication system Auth is very powerful and easy to use. However, in Laravel's user authentication system, users register, log in, and password recovery modules, they use bcrypt when using password encryption and authentication algorithms. Many projects that have been done before use use the salt + password encryption string to record the user's password. This has brought great resistance to using the Laravel framework to reconstruct previous projects. However, I have recently completed the modification of Laravel Auth by finding information online, reading community forums, and looking at source code. I hope it will be helpful to others. Before the beginning, we need to explain that if a new project applies the Laravel framework, there is no need to make any modifications to Auth. The default bcrypt encryption algorithm is a more secure and efficient encryption algorithm than salt + password.

Modify user registration

First, the artisan command is used to enable verification in laravel

php artisan make:auth

After executing the command, there will be one more static method call in the routes file (location: app/Http/).

Route::auth();

This Route is a Facade of Laravel (located in Illuminate\Support\Facades\Route). The auth method called is defined in the Illuminate\Routing\Router class. As shown below, you can see that some Auth-related routing rules are defined in the auth method.

/**
 * Register the typical authentication routes for an application.
 *
 * @return void
 */
public function auth()
{
 // Authentication Routes...
 $this->get('login', 'Auth\AuthController@showLoginForm');
 $this->post('login', 'Auth\AuthController@login');
 $this->get('logout', 'Auth\AuthController@logout');

 // Registration Routes...
 $this->get('register', 'Auth\AuthController@showRegistrationForm');
 $this->post('register', 'Auth\AuthController@register');

 // Password Reset Routes...
 $this->get('password/reset/{token?}', 'Auth\PasswordController@showResetForm');
 $this->post('password/email', 'Auth\PasswordController@sendResetLinkEmail');
 $this->post('password/reset', 'Auth\PasswordController@reset');
}

Through the routing rules, you can see that the controller method requested during registration is the register method of AuthController. This method is defined in the traits of \Illuminate\Foundation\Auth\RegistersUsers. AuthController introduces this traits in the class definition.

/**
 * Handle a registration request for the application.
 *
 * @param \Illuminate\Http\Request $request
 * @return \Illuminate\Http\Response
 */
public function register(Request $request)
{
 $validator = $this->validator($request->all());

 if ($validator->fails()) {
 $this->throwValidationException(
  $request, $validator
 );
 }

 Auth::guard($this->getGuard())->login($this->create($request->all()));

 return redirect($this->redirectPath());
}

In the register method, the user input data in the request will be verified first. You only need to define the verification rules for each input field in the AuthController validator method.

protected function validator(array $data)
{
 return Validator::make($data, [
 'name' => 'required|max:255',
 'email' => 'required|email|max:255|unique:user',
 'password' => 'required|size:40|confirmed',
 ]);
}

Then look down and after the verification is passed, Laravel will use the AuthController create method to generate a new user, and then log in with the new user's data.Auth::guard($this->getGuard())->login($this->create($request->all()));

Therefore, if we want to customize the encryption method of generating user passwords when registering, we only need to modify the create method of AuthController.

for example:

/**
 * Create a new user instance after a valid registration.
 *
 * @param array $data
 * @return User
 */
protected function create(array $data)
{
 $salt = Str::random(6);
 return User::create([
 'nickname' => $data['name'],
 'email' => $data['email'],
 'password' => sha1($salt . $data['password']),
 'register_time' => time(),
 'register_ip' => ip2long(request()->ip()),
 'salt' => $salt
 ]);
}

Modify user login

Before modifying login, we need to use the routing rules to see the specific controller and method of the login request. You can see in the auth method definition mentioned above.

 $this->get('login', 'Auth\AuthController@showLoginForm');
 $this->post('login', 'Auth\AuthController@login');
 $this->get('logout', 'Auth\AuthController@logout');

The verification login operation is in the login method of the \App\Http\Controllers\Auth\AuthController class. Open AuthController and find that Auth-related methods are introduced into the class through traits. When using the traits to be introduced in the class, PHP will copy the code in the traits into the class when compiling. This is the specific application scenario and use of the features introduced by PHP5.5. PlaceTake AuthController@loginThe method is actually defined in
\Illuminate\Foundation\Auth\AuthenticatesUsers

/**
 * Handle a login request to the application.
 *
 * @param \Illuminate\Http\Request $request
 * @return \Illuminate\Http\Response
 */
public function login(Request $request)
{
 $this->validateLogin($request);
 $throttles = $this->isUsingThrottlesLoginsTrait();

 if ($throttles && $lockedOut = $this->hasTooManyLoginAttempts($request)) {
 $this->fireLockoutEvent($request);

 return $this->sendLockoutResponse($request);
 }

 $credentials = $this->getCredentials($request);

 if (Auth::guard($this->getGuard())->attempt($credentials, $request->has('remember'))) {
 return $this->handleUserWasAuthenticated($request, $throttles);
 }

 if ($throttles && ! $lockedOut) {
 $this->incrementLoginAttempts($request);
 }

 return $this->sendFailedLoginResponse($request);
}

The main operation of login verification isAuth::guard($this->getGuard())->attempt($credentials, $request->has('remember'));This method is done in the callAuth::guard($this->getGuard()) What I got was \Illuminate\Auth\SessionGuard (For details, please see the source code in Auth Facade\Illuminate\Auth\AuthManager)

Let's take a look at how the attempt method is implemented in SessionGuard:

public function attempt(array $credentials = [], $remember = false, $login = true)
{
 $this->fireAttemptEvent($credentials, $remember, $login);

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

 if ($this->hasValidCredentials($user, $credentials)) {
 if ($login) {
  $this->login($user, $remember);
 }

 return true;
 }

 if ($login) {
 $this->fireFailedEvent($user, $credentials);
 }

 return false;
}

/**
 * Determine if the user matches the credentials.
 *
 * @param mixed $user
 * @param array $credentials
 * @return bool
 */

protected function hasValidCredentials($user, $credentials)
{
 return ! is_null($user) && $this->provider->validateCredentials($user, $credentials);
}

retrieveByCredentials is used to retrieve user data from the database using the passed in fields. ValidateCredentials is the actual process used to verify whether the password is correct.

What needs to be noted here is$this->providerThis provider is a provider that implements the \Illuminate\Contracts\Auth\UserProvider class. We see that there are two UserProvider implementations under the directory Illuminate\Auth, namely DatabaseUserProvider and EloquentUserProvider. However, when we verify the password, we verify it through. Look at the configuration file of auth.

'providers' => [
 'users' => [
 'driver' => 'eloquent',
 'model' => App\User::class, //This is the Model used by driver ],
],

The configuration here isdriver => eloquent, Then it is verified through the retrieveByCredentials of EloquentUserProvider. This EloquentUserProvider is injected during instantiation of SessionGuard. (For details, how to instantiate the corresponding provider and inject it into SessionGuard by reading the auth configuration file, please refer to the source code of the createSessionDriver method in \Illuminate\Auth\AuthManager)

Next, we continue to check the implementation of retrieveByCredentials and validateCredentials methods in EloquentUserProvider:

/**
 * Retrieve a user by the given credentials.
 *
 * @param array $credentials
 * @return \Illuminate\Contracts\Auth\Authenticatable|null
 */
public function retrieveByCredentials(array $credentials)
{
 if (empty($credentials)) {
 return;
 }

 $query = $this->createModel()->newQuery();
 foreach ($credentials as $key => $value) {
 if (! Str::contains($key, 'password')) {
  $query->where($key, $value);
 }
 }
 return $query->first();
}

/**
 * Validate a user against the given credentials.
 *
 * @param \Illuminate\Contracts\Auth\Authenticatable $user
 * @param array $credentials
 * @return bool
 */
public function validateCredentials(UserContract $user, array $credentials)
{
 $plain = $credentials['password'];

 return $this->hasher->check($plain, $user->getAuthPassword());
}

The above two methods retrieveByCredentials use fields other than password to retrieve user records from the database user table, such as querying user records with email, and then validateCredentials method is through$this->haser->checkComparing the entered password with the hashed password to verify that the password is correct.

Okay, it's obvious that we need to change it to our own password verification, just implement validateCredentials by yourself, and modify $this->hasher->check as our own password verification rules.

First we modify$user->getAuthPassword()Pass the salt and password of user tables in the database to validateCredentials
Modify App\ Add the following code

/**
 * The table associated to this model
 */
protected $table = 'user';//User table name is notlaravelThe agreed here needs to be specified
/**
  * Disable Laravel automatic management of timestamp columns
  */
public $timestamps = false;

/**
  * Override the default getAuthPassword method in Laravel, return the user's password and salt fields
  * @return type
  */
public function getAuthPassword()
{
 return ['password' => $this->attributes['password'], 'salt' => $this->attributes['salt']];
}

Then we build our own implementation of UserProvider interface and put it in a custom directory:

Create a new app/Foundation/Auth/

namespace App\Foundation\Auth;

use Illuminate\Auth\EloquentUserProvider;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Support\Str;

class AdminEloquentUserProvider extends EloquentUserProvider
{

 /**
  * Validate a user against the given credentials.
  *
  * @param \Illuminate\Contracts\Auth\Authenticatable $user
  * @param array $credentials
  */
 public function validateCredentials(Authenticatable $user, array $credentials) {
  $plain = $credentials['password'];
  $authPassword = $user->getAuthPassword();

  return sha1($authPassword['salt'] . $plain) == $authPassword['password'];
 }
}

Finally, we modify the auth configuration file and let Laravel use the provider we just defined when doing Auth verification.
Modify config/:

'providers' => [
 'users' => [
  'driver' => 'admin-eloquent',
  'model' => App\User::class,
 ]
]

Modify app/Provider/

public function boot(GateContract $gate)
{
 $this->registerPolicies($gate);

 \Auth::provider('admin-eloquent', function ($app, $config) {
  return New \App\Foundation\Auth\AdminEloquentUserProvider($app['hash'], $config['model']);
 });
}

The Auth::provider method is used to register the Provider constructor. This constructor is a Closure. The specific code of the provider method is implemented in the AuthManager file.

public function provider($name, Closure $callback)
{
 $this->customProviderCreators[$name] = $callback;

 return $this;
}

The closure returns the AdminEloquentUserProvider object for use by Laravel Auth. After these modifications, Laravel Auth uses the custom salt + password method when doing user login verification.

Modify and reset password

The workflow for resetting passwords for Laravel is:

  • Send an email with a reset password link to the mailbox of the user who needs to reset the password. The link will contain the user's email address and token.
  • The user clicks the link in the email to enter a new password on the Reset Password page. Laravel confirms by verifying email and token that the user is to initiate the reset password request and update the new password to the user's record in the data table.

The first step is to configure Laravel's email function. In addition, you need to create a new table password_resets in the database to store the user's email and corresponding tokens.

CREATE TABLE `password_resets` (
 `email` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
 `token` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
 `created_at` timestamp NOT NULL,
 KEY `password_resets_email_index` (`email`),
 KEY `password_resets_token_index` (`token`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

By resetting the submit address of the password form, you can see that the form submits the new password to /password/reset in post. Let’s first look at the auth-related route and determine the controller method corresponding to /password/reset.

 $this->post('password/reset', 'Auth\PasswordController@reset');

You can see that the corresponding controller method is the reset method of the \App\Http\Controllers\Auth\PasswordController class. This method is actually defined in the traits of \Illuminate\Foundation\Auth\ResetsPasswords. PasswordController introduces this traits.

/**
 * Reset the given user's password.
 *
 * @param \Illuminate\Http\Request $request
 * @return \Illuminate\Http\Response
 */
public function reset(Request $request)
{
 $this->validate(
  $request,
  $this->getResetValidationRules(),
  $this->getResetValidationMessages(),
  $this->getResetValidationCustomAttributes()
 );

 $credentials = $this->getResetCredentials($request);

 $broker = $this->getBroker();

 $response = Password::broker($broker)->reset($credentials, function ($user, $password) {
  $this->resetPassword($user, $password);
 });

 switch ($response) {
  case Password::PASSWORD_RESET:
   return $this->getResetSuccessResponse($response);
  default:
   return $this->getResetFailureResponse($request, $response);
 }
}

The method begins by verifying the input through validator, and then passes the new password and a closure object to the Password::broker($broker)->reset(); method. This method is defined in the \Illuminate\Auth\Passwords\PasswordBroker class.

/**
 * Reset the password for the given token.
 *
 * @param array $credentials
 * @param \Closure $callback
 * @return mixed
 */
public function reset(array $credentials, Closure $callback)
{
 // If the responses from the validate method is not a user instance, we will
 // assume that it is a redirect and simply return it from this method and
 // the user is properly redirected having an error message on the post.
 $user = $this->validateReset($credentials);

 if (! $user instanceof CanResetPasswordContract) {
  return $user;
 }

 $pass = $credentials['password'];

 // Once we have called this callback, we will remove this token row from the
 // table and return the response from this callback so the user gets sent
 // to the destination given by the developers from the callback return.
 call_user_func($callback, $user, $pass);

 $this->tokens->delete($credentials['token']);

 return static::PASSWORD_RESET;
}

In the PasswordBroker reset method, the program will first authenticate the data submitted by the user again, and then pass the password and user instance to the passed closure. In the closure call, the operation of updating the new password to the user table is completed. In the closure, the resetPassword method of the PasswrodController class called by the program in the closure.

function ($user, $password) {
 $this->resetPassword($user, $password);
});

Definition of PasswrodController class resetPassword method

protected function resetPassword($user, $password)
{
 $user->forceFill([
  'password' => bcrypt($password),
  'remember_token' => Str::random(60),
 ])->save();

 Auth::guard($this->getGuard())->login($user);
}

In this method, Laravel uses bcrypt to encrypt the password. Then, to change it to the salt + password method we need, we can rewrite the resetPassword method in the PasswordController class to overwrite the method in the traits.

/**
  * Overwrite the resetPassword method in ResetsPasswords traits, and use sha1(salt + password) encryption method
  * Reset the given user's password.
  *
  * @param \Illuminate\Contracts\Auth\CanResetPassword $user
  * @param string $password
  * @return void
  */
protected function resetPassword($user, $password)
{
 $salt = Str::random(6);
 $user->forceFill([
  'password' => sha1($salt . $password),
  'salt' => $salt,
  'remember_token' => Str::random(60),
 ])->save();

 \Auth::guard($this->getGuard())->login($user);
}

Conclusion

At this point, customizing Laravel Auth is completed. Registration, login and reset passwords have been changed to sha1 (salt + password) password encryption method. All custom codes are completed by defining subclasses and rewriting methods of Laravel-related classes to complete the source code without modifying Laravel, which not only maintains good scalability but also ensures that the project can be moved freely.

Note:Laravel version used is 5.2

Summarize

The above is the entire content of this article. I hope the content of this article will be of some help to your study or work. If you have any questions, you can leave a message to communicate. Thank you for your support.