Laravel logo

Introduction


Ever since Laravel 5.0, I've been consistently impressed with its in-built features. A standout is its rapid project setup with user authentication. Here’s a look:

  $ composer create-project --prefer-dist laravel/laravel <project-name>
  $ cd <project-name>
  $ php artisan migrate
  $ php artisan make:auth

With this, you have an authentication system. And while Laravel has its defaults, what if you need customization?

The Challenge


How can we create a password-less user system with just an email and name? Let's dig in.

The Solution


Rather than reinventing the wheel, we're using Laravel's default approach as if it's a password reset.

Application Flow:

  1. Collect from the user their first_name, last_name, and email.
  2. Generate a temporary password and an expiration token. Store one key in the password_resets table.
  3. Email the user with a link to set their password.

We'll modify the default users table migration to fit our needs:

  Schema::create('users', function (Blueprint $table) {
    $table->bigIncrements('id');
    $table->string('first_name');
    $table->string('last_name');
    $table->string('email')->unique();
    $table->string('password');
    $table->timestamps();
  });

Next, set up core functionalities:

  /**
    * Create a hashed key for the user.
    *  
    * @return string
    */
  protected function createHashedKey()
  {
      $key = config('app.key');

      if (Str::startsWith($key, 'base64:')) {
          $key = base64_decode(substr($key, 7));
      }

      $key = hash_hmac('sha256', Str::random(40), $key);
      return $key;
  }
  /**
    * Create a new token for the user.
    *
    * @return string
    */
  protected function createNewToken($hashedKey)
  {
      return Hash::make($hashedKey);
  }
  /**
    * @param $token the emailed token
    * @param $hashedKey the saved key on the password_resets
    * @return boolean
    */
  private function compareTokens($token, $hashedKey)
  {
    return Hash::check($token, $hashedKey);
  }
  /**
    * Delete expired tokens.
    *
    * @return void
    */
  private function deleteExpiredTokens()
  {
      $expiredAt = Carbon::now()->subSeconds(config('app.token_expiration_date'));

      PasswordReset::where('created_at', '<', $expiredAt)->delete();
  }

  /**
    * Delete a token record by user email.
    *
    * @param $email
    * @return void
    */
  private function delete($email)
  {
      PasswordReset::whereEmail($email)->delete();
  }

Implementation becomes straightforward. We create our token and keys:

  {
    //...
    $hashedKey = $this->createHashedKey();
    $token = $this->createNewToken($hashedKey);
    //...

Then save the generated token to our password_resets table as well as the user account with their temporary password:

  //..
  // $input comes from a validated Illuminate\Http\Request object.
  // A database transaction saves us from having incomplete data being stored.
  try {
    DB::transaction(function () use ($token, $input) {
      PasswordReset::create([
        'email' => $input['email'],
        'token' => $token,
        'created_at' => Carbon::now()->toDateTimeString()
      ]);
      User::create([
        'first_name' => $input['first_name'],
        'last_name' => $input['last_name'],
        'email' => $input['email'],
        'password' => Str::random()
      ]);
    });
  } catch (\PDOException $e) {
    // catch exception
  }
  //..

Finally we send the user an email containing the hashed token:

  //..
  // email user
  Mail::to($input['email'])->send(new AccountRegistration($input, $hashedKey));
  //...

Once a user receives their email with the password reset/account activation link. We will call the deleteExpiredTokens() look up the user by their email and compare the emailed hashedKey with the token we saved on the database. Once we do that we delete the token from the database.

  //...
  $this->deleteExpiredTokens();
  $user = PasswordReset::whereEmail($email)->first();
  if (!is_null($user)) {
      $result = $this->compareTokens($token, $user->token);
      if ($result) {
        $this->delete($email);
        // allow the user to reset their password
        // by displaying a front end view with the password reset fields.
      }
  }
  //...
}

In closing


Harnessing Laravel’s capabilities, you can now effortlessly provision users. Ready to tackle the frontend? Happy coding!