Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Password reset in Laravel by email or mobile

By default Laravel 5.5's password reset system works on email, but I need to add support for a mobile number (verify by OTP and generate a token and redirect to password reset page). I am doing all this part and I have created a mobile column on password_resets table.

But the problem is \Illuminate\Auth\Passwords\DatabaseTokenRepository && \Illuminate\Auth\Passwords\TokenRepositoryInterface ON exist method and it doesn't seem configurable.

public function exists(CanResetPasswordContract $user, $token)
{
    $record = (array) $this->getTable()->where(
        'email', $user->getEmailForPasswordReset()
    )->first();

    return $record &&
           ! $this->tokenExpired($record['created_at']) &&
             $this->hasher->check($token, $record['token']);
}

I need to override that method. There's so much inheritance going on. What classes do I need to extend and how to override that method.

like image 843
Hasan Hafiz Pasha Avatar asked Nov 16 '19 07:11

Hasan Hafiz Pasha


People also ask

How does Laravel password reset work?

First, we will need a pair of routes to handle allowing the user to request a password reset link via their email address. Second, we will need a pair of routes to handle actually resetting the password once the user visits the password reset link that is emailed to them and completes the password reset form.

How can I create API forgot password and change password in Laravel?

Register route in api. php file. Route::post('password/email', 'ForgotPasswordController@forgot'); Now, sendResetLink function will send a reset password link with email and token in url.


Video Answer


1 Answers

If you want to override behaviour of \Illuminate\Auth\Passwords\DatabaseTokenRepository methods, you will have to build your own token repository, overriding methods in the existing repository that currently just check for the 'email' column in the database. Ensure you've created a migration to add the appropriate column:

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    public function up(): void
    {
        $tname = config("auth.passwords.users.table");
        Schema::table($tname, fn (Blueprint $t) => $t->string("mobile", 16));
    }

    public function down(): void
    {
        $tname = config("auth.passwords.users.table");
        Schema::table($tname, fn (Blueprint $t) => $t->dropColumn("mobile"));
    }
}

Then create your custom repository:

app/Auth/DatabaseTokenRepository.php

<?php

namespace App\Auth;

use Illuminate\Auth\Passwords\DatabaseTokenRepository as DatabaseTokenRepositoryBase;
use Illuminate\Contracts\Auth\CanResetPassword as CanResetPasswordContract;
use Illuminate\Support\Carbon;

class DatabaseTokenRepository extends DatabaseTokenRepositoryBase;
{
    //
    // Override these methods to use mobile as well as email
    //
    public function create(CanResetPasswordContract $user)
    {
        $email = $user->getEmailForPasswordReset();
        $mobile = $user->getMobileForPasswordReset();
        $this->deleteExisting($user);
        $token = $this->createNewToken();
        $this->getTable()->insert($this->getPayload($email, $mobile, $token));
        return $token;
    }

    protected function deleteExisting(CanResetPasswordContract $user)
    {
        return $this->getTable()
            ->where("email", $user->getEmailForPasswordReset())
            ->orWhere("mobile", $user->getMobileForPasswordReset())
            ->delete();
    }

    protected function getPayload($email, $mobile, $token): array
    {
        return [
            "email" => $email,
            "mobile" => $mobile,
            "token" => $this->hasher->make($token),
            "created_at" => new Carbon(),
        ];
    }

    public function exists(CanResetPasswordContract $user, $token)
    {
        $record = (array) $this->getTable()
            ->where("email", $user->getEmailForPasswordReset())
            ->orWhere("mobile", $user->getMobileForPasswordReset())
            ->first();
        return $record &&
               ! $this->tokenExpired($record["created_at"]) &&
                 $this->hasher->check($token, $record["token"]);
    }

    public function recentlyCreatedToken(CanResetPasswordContract $user)
    {
        $record = (array) $this->getTable()
            ->where("email", $user->getEmailForPasswordReset())
            ->orWhere("mobile", $user->getMobileForPasswordReset())
            ->first();

        return $record && $this->tokenRecentlyCreated($record['created_at']);
    }
}

Now you will need to use this custom token repository instead of the default one. So you have to override another class.

app/Auth/PasswordBrokerManager.php

<?php
namespace App\Auth;

use Illuminate\Support\Str;
use Illuminate\Auth\Passwords\PasswordBrokerManager as PasswordBrokerManagerBase;

class PasswordBrokerManager extends PasswordBrokerManagerBase
{
    protected function createTokenRepository(array $config)
    {
        $key = $this->app['config']['app.key'];
        if (Str::startsWith($key, 'base64:')) {
            $key = base64_decode(substr($key, 7));
        }
        $connection = $config['connection'] ?? null;
        // return an instance of your new repository
        // it's in the same namespace, no need to alias it
        return new DatabaseTokenRepository(
            $this->app['db']->connection($connection),
            $this->app['hash'],
            $config['table'],
            $key,
            $config['expire']
        );
    }
}

Now you have created a custom broker to use your custom repository. You need a new service provider to make use of it.

app/Providers/PasswordResetServiceProvider.php

<?php
namespace App\Providers;

use App\Auth\PasswordBrokerManager;
use Illuminate\Auth\Passwords\PasswordResetServiceProvider as PasswordResetServiceProviderBase;

class PasswordResetServiceProvider extends PasswordResetServiceProviderBase
{
    protected function registerPasswordBroker()
    {
        $this->app->singleton('auth.password', function ($app) {
            // reference your new broker
            // the rest of the method code is unchanged
            return new PasswordBrokerManager($app);
        });
        $this->app->bind('auth.password.broker', function ($app) {
            return $app->make('auth.password')->broker();
        });
    }
}

Next, replace default password reset service provider with the custom one in your application config: app/config/app.php

<?php

return [
    "providers" => [
        ...
        // Illuminate\Auth\Passwords\PasswordResetServiceProvider::class,
        App\Providers\PasswordResetServiceProvider::class,
        ...
    ],
];

And finally, define the getMobileForPasswordReset() method on your user model:

<?php

namespace App;

use Illuminate\Foundation\Auth\User as Authenticatable;
...

class User extends Authenticatable
{
    ...
    public method getMobileForPasswordReset()
    {
        return $this->mobile;
    }
}
like image 54
miken32 Avatar answered Oct 17 '22 20:10

miken32